Questo comportamento di vector :: resize (size_type n) in C ++ 11 e Boost.Container è corretto?

Ho un’applicazione C ++ 03 dove i tipi std::vector sono usati come buffer temporanei. In quanto tali, vengono spesso ridimensionati usando std::vector::resize() per assicurarsi che siano abbastanza grandi da contenere i dati richiesti prima dell’uso. Il prototipo C ++ 03 per questa funzione è in realtà:

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

Quindi in realtà quando si chiama resize() , il vettore viene ingrandito aggiungendo il numero appropriato di copie di val . Spesso, tuttavia, ho solo bisogno di sapere che il vector è abbastanza grande da contenere i dati di cui ho bisogno; Non ne ho bisogno inizializzato con alcun valore. Copiare-build i nuovi valori è solo una perdita di tempo.

C ++ 11 viene in soccorso (pensavo): nelle sue specifiche, divide resize() in due overload:

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

Questo si adatta perfettamente alla filosofia del C ++: paga solo per quello che vuoi. Come ho notato, però, la mia applicazione non può usare C ++ 11, quindi ero felice quando ho trovato la libreria Boost.Container, che indica il supporto per questa funzionalità nella sua documentazione. In particolare, boost::container::vector ha in realtà tre overload di 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 

Per verificare di aver capito tutto, ho eseguito un rapido test per verificare il comportamento di C ++ 11 std::vector e boost::container::vector :

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

Compilare questo con g++ 4.8.1 in modalità C ++ 03 come segue:

 g++ vectest.cc ./a.out 

produce il seguente risultato:

 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 

Questo non è troppo sorprendente. Mi aspetto che C ++ 03 std::vector inizializzi gli ultimi 5 elementi con zeri. Posso persino convincermi perché boost::container::vector sta facendo lo stesso (suppongo che emuli il comportamento di C ++ 03 in modalità C ++ 03). Ho solo ottenuto l’effetto che volevo quando chiedevo specificamente l’inizializzazione predefinita. Tuttavia, quando ho ricostruito in modalità C ++ 11 come segue:

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

Ottengo questi risultati:

 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 

Esattamente la stessa! Che porta alla mia domanda:

Ho sbagliato a pensare che dovrei vedere gli stessi risultati di ciascuno dei tre test in questo caso? Ciò sembra indicare che il cambio di interfaccia std::vector non ha avuto alcun effetto, in quanto i 5 elementi aggiunti nell’ultima chiamata a resize() ancora inizializzati con zeri nei primi due casi.

Non una risposta, ma una lunga appendice a Howard : io uso un adattatore allocatore che funziona fondamentalmente come l’allocatore di Howard, ma è più sicuro dal

  1. si interpone solo sull’inizializzazione del valore e non su tutte le inizializzazioni,
  2. è correttamente inizializzato in modo predefinito.
 // 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)...); } }; 

C’è una piccola differenza funzionale con le firme di resize C ++ 11, ma il test non la esporrà. Considera questo test simile:

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

Sotto C ++ 03 questa stampa:

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

Ma sotto C ++ 11 stampa:

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

La motivazione di questo cambiamento è di supportare meglio i tipi non trasferibili (solo spostamento) nel vector . Il più delle volte, incluso nel tuo caso, questo cambiamento non fa differenza.

C’è un modo per realizzare ciò che vuoi in C ++ 11 con l’uso di un allocatore personalizzato (che il tuo compilatore può o non può ancora supportare):

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

Quale dovrebbe produrre:

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

Il no_init_alloc si rifiuta semplicemente di fare qualsiasi inizializzazione, che va bene per int , lasciandolo con un valore non specificato. Ho dovuto cambiare il tuo init_vec per usare il compito di inizializzare invece di usare la costruzione. Quindi questo può essere pericoloso / confusionario se non si presta attenzione. Tuttavia evita di eseguire un’inizializzazione non necessaria.

Quindi in realtà quando si chiama ridimensiona (), il vettore viene ingrandito aggiungendo il numero appropriato di copie di val. Spesso, tuttavia, ho solo bisogno di sapere che il vettore è abbastanza grande da contenere i dati di cui ho bisogno; Non ne ho bisogno inizializzato con alcun valore. Copiare-build i nuovi valori è solo una perdita di tempo.

No, non proprio. Avere un contenitore di elementi che non sono realmente costruiti non ha senso. Non sono sicuro di cosa ti aspettavi di vedere oltre gli zero. Elementi non specificati / non inizializzati? Questo non è il significato di inizializzazione del valore.

Se hai bisogno di N elementi, allora dovresti avere N elementi correttamente costruiti, e questo è ciò che fa std::vector::resize . L’inizializzazione del valore azzererà l’inizializzazione di un object senza alcun costrutto predefinito da invocare, quindi in realtà è l’opposto di ciò che sembra, il che è meno sicurezza e inizializzazione piuttosto che altro.

Suggerisco che quello che stai std::vector::reserve è std::vector::reserve .

Questo sembra indicare che il cambio di interfaccia std::vector non ha davvero avuto alcun effetto

Certamente ha un effetto, ma non quello che stai cercando. Il nuovo sovraccarico di resize è per comodità, quindi non è necessario creare il proprio temporaneo quando l’inizializzazione di default o di valore è tutto ciò che serve. Non è un cambiamento fondamentale nel modo in cui funzionano i contenitori, ovvero che tengono sempre valide istanze di tipi .

Valido ma in uno stato non specificato se ti sposti da loro!

Valori non inizializzati

Potresti avere un valore inizializzato creando la class appropriata. Come il seguente:

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

L’output è identico a “boost w / default”.

O creare un allocatore personalizzato con construct e destroy come nop.

Splitting prototipo di resize

Se void std::vector::resize(size_type n) fa cosa vuol dire void bc::vector::resize(size_type n, default_init_t) , allora molto vecchio codice valido si romperà …

La divisione di resize() consente di ridimensionare il vettore delle classi ‘move only’ come segue:

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

Inizializzazione del valore dei rendimenti int 0.

L’inizializzazione di default di int non inizializza il valore – mantiene solo tutto ciò che era in memoria.

O la memoria allocata per resize(10) non è stata rilasciata da resize(5) , o lo stesso blocco di memoria è stato riutilizzato. In entrambi i casi hai finito con i contenuti precedenti rimasti.

se vuoi usare un vettore con l’allocatore standard, non funziona 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)); };