Vecteur C ++ qui * n’initialise pas ses membres?

Je fais un wrapper C ++ pour un morceau de code C qui retourne un grand tableau, et j’ai donc essayé de retourner les données dans un vector .

Maintenant, le problème est que les données sont de l’ordre de mégaoctets et que vector initialise inutilement son stockage, ce qui s’avère essentiellement réduire de moitié ma vitesse.

Comment puis-je empêcher cela?

Ou, si ce n’est pas possible – y a-t-il un autre conteneur STL qui permettrait d’éviter un tel travail inutile? Ou dois-je finir par fabriquer mon propre conteneur?

(Pre-C ++ 11)

Remarque:

Je passe le vecteur comme mon tampon de sortie . Je ne copie pas les données d’ailleurs.
C’est quelque chose comme:

 vector buf(size); // Why initialize?? GetMyDataFromC(&buf[0], buf.size()); 

Pour l’initialisation par défaut et par valeur des structures avec des constructeurs par défaut fournis par l’utilisateur qui n’initialisent pas explicitement quoi que ce soit, aucune initialisation n’est effectuée sur les membres non signés:

 struct uninitialized_char { unsigned char m; uninitialized_char() {} }; // just to be safe static_assert(1 == sizeof(uninitialized_char), ""); std::vector v(4 * (1<<20)); GetMyDataFromC(reinterpret_cast(&v[0]), v.size()); 

Je pense que cela est même légal selon les règles ssortingctes d’aliasing.

Lorsque j’ai comparé le temps de construction pour v vs un vector j’ai obtenu ~ 8 µs vs ~ 12 ms. Plus de 1000 fois plus rapide. Le compilateur était clang 3.2 avec libc ++ et flags: -std=c++11 -Os -fcatch-undefined-behavior -ftrapv -pedantic -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-missing-prototypes

C ++ 11 a une aide pour le stockage non initialisé, std :: align_storage. Bien qu’il nécessite une taille de compilation.


Voici un exemple supplémentaire, pour comparer l’utilisation totale (fois en nanosecondes):

VERSION = 1 ( vector ):

 clang++ -std=c++14 -stdlib=libc++ main.cpp -DVERSION=1 -ftrapv -Weverything -Wno-c++98-compat -Wno-sign-conversion -Wno-sign-compare -Os && ./a.out initialization+first use: 16,425,554 array initialization: 12,228,039 first use: 4,197,515 second use: 4,404,043 

VERSION = 2 ( vector ):

 clang++ -std=c++14 -stdlib=libc++ main.cpp -DVERSION=2 -ftrapv -Weverything -Wno-c++98-compat -Wno-sign-conversion -Wno-sign-compare -Os && ./a.out initialization+first use: 7,523,216 array initialization: 12,782 first use: 7,510,434 second use: 4,155,241 

 #include  #include  #include  struct uninitialized_char { unsigned char c; uninitialized_char() {} }; void foo(unsigned char *c, int size) { for (int i = 0; i < size; ++i) { c[i] = '\0'; } } int main() { auto start = std::chrono::steady_clock::now(); #if VERSION==1 using element_type = unsigned char; #elif VERSION==2 using element_type = uninitialized_char; #endif std::vector v(4 * (1<<20)); auto end = std::chrono::steady_clock::now(); foo(reinterpret_cast(v.data()), v.size()); auto end2 = std::chrono::steady_clock::now(); foo(reinterpret_cast(v.data()), v.size()); auto end3 = std::chrono::steady_clock::now(); std::cout.imbue(std::locale("")); std::cout << "initialization+first use: " << std::chrono::nanoseconds(end2-start).count() << '\n'; std::cout << "array initialization: " << std::chrono::nanoseconds(end-start).count() << '\n'; std::cout << "first use: " << std::chrono::nanoseconds(end2-end).count() << '\n'; std::cout << "second use: " << std::chrono::nanoseconds(end3-end2).count() << '\n'; } 

J'utilise clang svn-3.6.0 r218006

Désolé, il n’y a aucun moyen de l’éviter.

C ++ 11 ajoute un constructeur qui ne prend qu’une taille, mais même cela va initialiser les données.

Votre meilleur pari est de simplement allouer un tableau sur le tas, de le coller dans un unique_ptr (le cas échéant), et de l’utiliser à partir de là.

Si vous souhaitez, comme vous le dites, «pirater en STL», vous pouvez toujours récupérer une copie d’EASTL sur laquelle vous pouvez travailler. C’est une variation de certains conteneurs STL qui permet des conditions de mémoire plus restreintes. Une implémentation correcte de ce que vous essayez de faire serait de donner à son constructeur une valeur spéciale qui signifie “initialiser les membres par défaut”, ce qui pour les types de POD signifie ne rien faire pour initialiser la mémoire. Cela nécessite l’utilisation de métaprogrammation de modèle pour détecter s’il s’agit d’un type POD, bien sûr.

1 Il semble que l’utilisation de std::vector ne soit ni nécessaire ni raisonnable dans votre situation. Vous ne voulez qu’un object pour gérer de la mémoire brute pour vous. Cela peut être facilement réalisé par

 std::unique_ptr p(std::malloc(n), std::free); 

2 Si vous voulez vraiment utiliser std::vector<> vous pouvez utiliser l’astuce décrite ici .

La solution optimale consiste simplement à modifier l’allocateur pour qu’il ne fasse rien pour une construct arguments. Cela signifie que le type sous-jacent est le même, ce qui évite toute sorte de violation réinterprétée et de violations potentielles d’alias et peut désinitialiser de manière non intrusive tout type.

 template struct no_initialize : std::allocator { void construct(T* p) {} template void construct(T* p, Args&&... args) { new (p) T(std::forward(args)...); } }; 

Que diriez-vous d’utiliser vector.reserve () pour allouer uniquement le stockage, mais pas pour l’initialiser?