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.

reconnaitre
la source
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.

eerorika
la source
1
Merci de m'avoir corrigé. Edité. Bon point avec réserve.
reconnaissez le