Puis-je list-initialiser std :: vector avec une transmission parfaite des éléments?


14

J'ai remarqué que globale liste initalization de std :: vecteur effectue l' initialisation de copie lors de déplacement est plus applicable. En même temps, plusieurs emplace_backs font ce que je veux.

Je ne pouvais que trouver cette solution imparfaite d'écrire une fonction de modèle init_emplace_vector. Cependant, il n'est optimal que pour les constructeurs à valeur unique non explicites .

template <typename T, typename... Args>
std::vector<T> init_emplace_vector(Args&&... args)
{
  std::vector<T> vec;
  vec.reserve(sizeof...(Args));  // by suggestion from user: eerorika
  (vec.emplace_back(std::forward<Args>(args)), ...);  // C++17
  return vec;
}

Question

Ai-je vraiment besoin d'utiliser emplace_back pour initialiser std :: vector aussi efficacement que possible?

// an integer passed to large is actually the size of the resource
std::vector<large> v_init {
  1000,  // instance of class "large" is copied
  1001,  // copied
  1002,  // copied
};

std::vector<large> v_emplaced;
v_emplaced.emplace_back(1000);  // moved
v_emplaced.emplace_back(1001);  // moved
v_emplaced.emplace_back(1002);  // moved

std::vector<large> v_init_emplace = init_emplace_vector<large>(
  1000,   // moved
  1001,   // moved
  1002    // moved
);

Production

La classe largeproduit des informations sur les copies / mouvements (implémentation ci-dessous) et donc la sortie de mon programme est:

- initializer
large copy
large copy
large copy
- emplace_back
large move
large move
large move
- init_emplace_vector
large move
large move
large move

Implémentation de grande classe

Mon implémentation de largeest simplement un type copiable / mobile contenant une grande ressource qui avertit lors de la copie / déplacement.

struct large
{
  large(std::size_t size) : size(size), data(new int[size]) {}

  large(const large& rhs) : size(rhs.size), data(new int[rhs.size])
  {
    std::copy(rhs.data, rhs.data + rhs.size, data);
    std::puts("large copy");
  }

  large(large&& rhs) noexcept : size(rhs.size), data(rhs.data)
  {
    rhs.size = 0;
    rhs.data = nullptr;
    std::puts("large move");
  }

  large& operator=(large rhs) noexcept
  {
    std::swap(*this, rhs);
    return *this;
  }

  ~large() { delete[] data; }

  int* data;
  std::size_t size;
};

Éditer

En utilisant reserve, il n'y a pas de copie ni de déplacement. Seul le large::large(std::size_t)constructeur est invoqué. Véritable lieu de travail.


1
Ce n'est pas une initialisation d'agrégat, vous appelez le constructeur qui prend a std::initializer_list.
super

Les constructeurs de std :: vector sont un désordre déroutant à mon humble avis, et si j'étais vous, j'éviterais vraiment d'y entrer si je ne suis pas absolument obligé. De plus, si vous savez à l'avance le contenu de vos vecteurs (ce qui semble être le cas), pensez à un std::array.
einpoklum

1
Un operator=élément qui détruit la source est assez inhabituel et peut provoquer des problèmes inattendus.
Alain

1
init_emplace_vectorpourrait être amélioré avecvec.reserve(sizeof...(Args))
Indiana Kernick

1
@alain operator=ne détruit pas la source. C'est l'idiome d'échange de copie.
reconnaissez le

Réponses:


11

Puis-je agréger-initialiser std :: vector ...

Non std::vectorn'est pas un agrégat, il ne peut donc pas être initialisé par agrégat.

Vous pouvez plutôt signifier l'initialisation de la liste, auquel cas:

Puis-je [list-initialiser] std :: vector avec une transmission parfaite des éléments?

Non. L'initialisation de liste utilise le std::initializer_listconstructeur et std::initializer_listcopie ses arguments.

Votre init_emplace_vectorsemble être une solution décente, bien qu'elle puisse être améliorée en réservant la mémoire avant de placer les éléments.


1
Merci de m'avoir corrigé. Edité. Bon point avec réserve.
reconnaissez le
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.