Kann ich std :: vector mit perfekter Weiterleitung der Elemente listeninitialisieren?

14

Ich bemerkte , dass aggregierte Liste initialisiert von std :: vector führt Initialisierung kopieren , wenn Bewegung mehr anwendbar ist. Gleichzeitig machen mehrere emplace_backs, was ich will.

Ich konnte mir nur diese unvollständige Lösung für das Schreiben einer Vorlagenfunktion einfallen lassen init_emplace_vector. Es ist jedoch nur für nicht explizite Einzelwertkonstruktoren optimal .

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;
}

Frage

Muss ich wirklich emplace_back verwenden, um std :: vector so effizient wie möglich zu initialisieren?

// 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
);

Ausgabe

Die Klasse largeerzeugt Informationen über Kopien / Verschiebungen (Implementierung unten) und daher lautet die Ausgabe meines Programms:

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

Implementierung einer großen Klasse

Meine Implementierung von largeist einfach ein kopierbarer / verschiebbarer Typ, der eine große Ressource enthält, die beim Kopieren / Verschieben warnt.

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;
};

Bearbeiten

Durch die Verwendung von Reserve erfolgt keine Kopie oder Verschiebung. Es wird nur der large::large(std::size_t)Konstruktor aufgerufen. True Emplace.

reconn
quelle
1
Es ist keine Aggregatinitialisierung, Sie rufen den Konstruktor auf, der a benötigt std::initializer_list.
Super
Die Konstruktoren von std :: vector sind meiner Meinung nach ein verwirrendes Durcheinander, und wenn ich Sie wäre, würde ich es wirklich vermeiden, mich darauf einzulassen, wenn ich es nicht unbedingt muss. Wenn Sie den Inhalt Ihrer Vektoren im Voraus kennen (was anscheinend der Fall ist), sollten Sie eine std::array.
Einpoklum
1
Eine operator=, die die Quelle zerstört, ist ziemlich ungewöhnlich und kann zu unerwarteten Problemen führen.
Alain
1
init_emplace_vectorkönnte verbessert werden mitvec.reserve(sizeof...(Args))
Indiana Kernick
1
@alain operator=zerstört die Quelle nicht. Es ist die Copy-Swap-Sprache.
Am

Antworten:

11

Kann ich std :: vector aggregieren und initialisieren ...

Nein std::vectorist kein Aggregat, daher kann es nicht aggregatinitialisiert werden.

Sie können stattdessen Listeninitialisierung bedeuten. In diesem Fall:

Kann ich std :: vector mit perfekter Weiterleitung der Elemente [list-initialize] ?

Die Listeninitialisierung verwendet den std::initializer_listKonstruktor und std::initializer_listkopiert seine Argumente.

Ihre init_emplace_vectorLösung scheint anständig zu sein, obwohl sie verbessert werden kann, indem Sie den Speicher reservieren, bevor Sie die Elemente einfügen.

Eerorika
quelle
1
Danke, dass du mich verbessert hast. Bearbeitet. Guter Punkt mit Reserve.
Am