Ist dieses Verhalten von vector :: resize (size_type n) unter C ++ 11 und Boost.Container korrekt?

Ich habe eine C ++ 03 Anwendung, wo std::vector -Typen überall als temporäre Puffer verwendet werden. Daher werden sie oft mit std::vector::resize() um sicherzustellen, dass sie groß genug sind, um die benötigten Daten vor der Verwendung zu speichern. Der C ++ 03-Prototyp für diese function ist eigentlich:

 void resize(size_type n, value_type val = value_type()); 

In Wirklichkeit wird beim Aufrufen von resize() der Vektor vergrößert, indem die entsprechende Anzahl von Kopien von val hinzugefügt wird. Oft muss ich jedoch nur wissen, dass der vector groß genug ist, um die Daten zu speichern, die ich brauche. Ich brauche es nicht mit irgendeinem Wert initialisiert. Das Kopieren der neuen Werte ist nur eine Zeitverschwendung.

C ++ 11 kommt zur Rettung (ich dachte): in seiner Spezifikation spaltet resize() in zwei Überladungen auf:

 void resize(size_type n); // value initialization void resize(size_type n, const value_type &val); // initialization via copy 

Das passt gut zur Philosophie von C ++: Zahlen Sie nur, was Sie wollen. Wie ich bereits angemerkt habe, kann meine Anwendung C ++ 11 nicht verwenden, daher war ich froh, als ich auf die Boost.Container-Bibliothek stieß, die die Unterstützung für diese functionalität in ihrer Dokumentation angibt . Insbesondere hat boost::container::vector tatsächlich drei Überladungen von resize() :

 void resize(size_type n); // value initialization void resize(size_type n, default_init_t); // default initialization void resize(size_type n, const value_type &val); // initialization via copy 

Um zu überprüfen, dass ich alles verstanden habe, habe ich einen kurzen Test durchgeführt, um das Verhalten von C ++ 11 std::vector und boost::container::vector zu überprüfen:

 #include  #include  #include  using namespace std; namespace bc = boost::container; template  void init_vec(VecType &v) { // fill v with values [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] for (size_t i = 0; i < 10; ++i) v.push_back(i); // chop off the end of v, which now should be [1, 2, 3, 4, 5], but the other 5 values // should remain in memory v.resize(5); } template  void print_vec(const char *label, VecType &v) { cout << label << ": "; for (size_t i = 0; i < v.size(); ++i) { cout << v[i] << ' '; } cout << endl; } int main() { // instantiate a vector of each type that we're going to test std::vector std_vec; bc::vector boost_vec; bc::vector boost_vec_default; // fill each vector in the same way init_vec(std_vec); init_vec(boost_vec); init_vec(boost_vec_default); // now resize each vector to 10 elements in ways that *should* avoid reinitializing the new elements std_vec.resize(10); boost_vec.resize(10); boost_vec_default.resize(10, bc::default_init); // print each one out print_vec("std", std_vec); print_vec("boost", boost_vec); print_vec("boost w/default", boost_vec_default); } 

Kompilieren Sie dies mit g++ 4.8.1 im C ++ 03-Modus wie folgt:

 g++ vectest.cc ./a.out 

ergibt folgende Ausgabe:

 std: 0 1 2 3 4 0 0 0 0 0 boost: 0 1 2 3 4 0 0 0 0 0 boost w/default: 0 1 2 3 4 5 6 7 8 9 

Das ist nicht zu überraschend. Ich erwarte, dass C ++ 03 std::vector die letzten 5 Elemente mit Nullen initialisiert. Ich kann mich sogar selbst davon überzeugen, warum boost::container::vector dasselbe tut (ich würde annehmen, dass es C ++ 03-Verhalten im C ++ 03-Modus emuliert). Ich habe nur den Effekt, den ich wollte, als ich speziell nach der Standardinitialisierung fragte. Jedoch, wenn ich im C ++ 11-Modus wie folgt neu aufgebaut:

 g++ vectest.cc -std=c++11 ./a.out 

Ich bekomme diese Ergebnisse:

 std: 0 1 2 3 4 0 0 0 0 0 boost: 0 1 2 3 4 0 0 0 0 0 boost w/default: 0 1 2 3 4 5 6 7 8 9 

Genauso! Was zu meiner Frage führt:

Habe ich falsch gedacht, dass ich bei jedem der drei Tests in diesem Fall die gleichen Ergebnisse sehen sollte? Dies scheint darauf hinzuweisen, dass die Änderung der Schnittstelle std::vector keine Auswirkung hatte, da die 5 Elemente, die beim letzten Aufruf von resize() hinzugefügt wurden, in den ersten beiden Fällen immer noch mit Nullen initialisiert werden.

   

Keine Antwort, aber ein langwieriger Zusatz zu Howards : Ich benutze einen Allokator-Adapter, der im Prinzip genauso funktioniert wie Howards Allokator, aber sicherer seither

  1. es interpoliert nur auf Wert-Initialisierung und nicht alle Initialisierungen,
  2. Es wird korrekt initialisiert.
 // Allocator adaptor that interposes construct() calls to // convert value initialization into default initialization. template > class default_init_allocator : public A { typedef std::allocator_traits a_t; public: template  struct rebind { using other = default_init_allocator< U, typename a_t::template rebind_alloc >; }; using A::A; template  void construct(U* ptr) noexcept(std::is_nothrow_default_constructible::value) { ::new(static_cast(ptr)) U; } template  void construct(U* ptr, Args&&... args) { a_t::construct(static_cast(*this), ptr, std::forward(args)...); } }; 

Es gibt einen kleinen funktionalen Unterschied zu den C ++ 11- resize , aber Ihr Test wird es nicht verfügbar machen. Betrachten Sie diesen ähnlichen Test:

 #include  #include  struct X { X() {std::cout < < "X()\n";} X(const X&) {std::cout << "X(const X&)\n";} }; int main() { std::vector v; v.resize(5); } 

Unter C ++ 03 druckt dies:

 X() X(const X&) X(const X&) X(const X&) X(const X&) X(const X&) 

Aber unter C ++ 11 druckt es:

 X() X() X() X() X() 

Die Motivation für diese Änderung besteht darin, nicht kopierbare (nur Bewegung) Typen im vector besser zu unterstützen. Die meiste Zeit, einschließlich in Ihrem Fall, macht diese Änderung keinen Unterschied.

Es gibt einen Weg, um zu erreichen, was Sie in C ++ 11 mit dem Gebrauch eines kundenspezifischen Zuordners erreichen möchten (das Ihr Compiler vielleicht oder noch nicht unterstützt):

 #include  #include  using namespace std; template  class no_init_alloc : public std::allocator { public: using std::allocator::allocator; template  void construct(U*, Args&&...) {} }; template  void init_vec(VecType &v) { // fill v with values [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] v.resize(10); for (size_t i = 0; i < 10; ++i) v[i] = i; // Note this change!!! // chop off the end of v, which now should be [1, 2, 3, 4, 5], but the other 5 values // should remain in memory v.resize(5); } template  void print_vec(const char *label, VecType &v) { cout < < label << ": "; for (size_t i = 0; i < v.size(); ++i) { cout << v[i] << ' '; } cout << endl; } int main() { std::vector> std_vec; init_vec(std_vec); std_vec.resize(10); print_vec("std", std_vec); } 

Was sollte ausgeben:

 std: 0 1 2 3 4 5 6 7 8 9 

Das ” no_init_alloc ” weigert sich einfach, irgendeine Initialisierung no_init_alloc , was für int Ordnung ist und es mit einem unspezifizierten Wert no_init_alloc . Ich musste dein init_vec ändern, um die Zuweisung zu initialisieren, anstatt die Konstruktion zu verwenden. Dies kann gefährlich / verwirrend sein, wenn Sie nicht vorsichtig sind. Es vermeidet jedoch unnötige Initialisierung.

In Wirklichkeit wird beim Aufrufen von resize () der Vektor vergrößert, indem die entsprechende Anzahl von Kopien von val hinzugefügt wird. Oft muss ich jedoch nur wissen, dass der Vektor groß genug ist, um die Daten zu speichern, die ich brauche. Ich brauche es nicht mit irgendeinem Wert initialisiert. Das Kopieren der neuen Werte ist nur eine Zeitverschwendung.

Nein nicht wirklich. Einen Container mit Elementen zu haben, die nicht wirklich konstruiert sind, macht keinen Sinn. Ich bin mir nicht sicher, was Sie erwarten, andere als Nullen zu sehen. Nicht spezifizierte / nicht initialisierte Elemente? Das ist nicht was Wert-Initialisierung bedeutet.

Wenn Sie N Elemente benötigen, dann sollten Sie N richtig konstruierte Elemente haben, und das ist was std::vector::resize tut. Die Initialisierung von Werten initialisiert ein Objekt, für das kein Standardkonstruktor aufgerufen werden muss, also ist es das Gegenteil von dem, was Sie zu haben scheinen, was weniger Sicherheit und Initialisierung als mehr bedeutet.

Ich schlage vor, dass das, was Sie wirklich wollen, std::vector::reserve .

Dies scheint darauf hinzuweisen, dass die Änderung der Schnittstelle std::vector nicht wirklich Wirkung gezeigt hat

Es hat sicherlich eine Wirkung, nur nicht die, nach der Sie suchen. Die neue resize ist der Einfachheit halber so, dass Sie kein eigenes temporäres Konstrukt erstellen müssen, wenn die Initialisierung – oder sogar der Wert – nur die Initialisierung benötigt. Es ist keine grundlegende Änderung der functionsweise von Containern, da sie immer gültige Instanzen von Typen enthalten .

Gültig, aber in einem unspezifizierten Zustand, wenn Sie von ihnen weggehen!

Nicht initialisierte Werte

Möglicherweise haben Sie den Wert initialisiert, indem Sie die entsprechende class erstellt haben. Wie die folgenden:

 class uninitializedInt { public: uninitializedInt() {}; uninitializedInt(int i) : i(i) {}; operator int () const { return i; } private: int i; }; 

Die Ausgabe ist identisch mit “Boost w / default”.

Oder erstellen Sie einen benutzerdefinierten Zuordner mit construct und destroy als NOP.

Prototyp der resize aufteilen

Wenn void std::vector::resize(size_type n) tut, was void bc::vector::resize(size_type n, default_init_t) tut, dann würde viel alter gültiger Code brechen …

Die Teilungen von resize() erlauben es, den Vektor von “Nur bewegen” -classn wie folgt zu skalieren:

 class moveOnlyInt { public: moveOnlyInt() = default; moveOnlyInt(int i) : i(i) {}; moveOnlyInt(const moveOnlyInt&) = delete; moveOnlyInt(moveOnlyInt&&) = default; moveOnlyInt& operator=(const moveOnlyInt&) = delete; moveOnlyInt& operator=(moveOnlyInt&&) = default; operator int () const { return i; } private: int i; }; 

Wert Initialisierung von int ergibt 0.

Die Standardinitialisierung von int initialisiert den Wert überhaupt nicht – er behält nur den Inhalt des Speichers bei.

Entweder wurde der durch resize(10) zugewiesene Speicher nicht durch resize(5) freigegeben, oder der gleiche Speicherblock wurde wiederverwendet. So oder so hast du den vorherigen Inhalt übrig gelassen.

Wenn Sie einen Vektor mit dem Standard-Allokator verwenden möchten, funktioniert das nicht in C ++ 11 ??

  namespace{ struct Uninitialised {}; template template std::allocator::construct(U* , Uninitialised&&) { /*do nothing*/ }; } template void resize_uninitialised(std::vector& vec, std::vector::size_type size) { const Uninitialised* p = nullptr; auto cur_size = vec.size(); if(size < = cur_size) return; vec.reserve(size); //this should optimise to vec.m_size += (size - cur_size); //one cannot help thinking there must be simpler ways to do that. vec.insert(vec.end(), p, p + (size - cur_size)); };