Conteneurs de bibliothèque standard produisant beaucoup de copies sur des valeurs dans GCC

J’écris une application pour Linux et Windows, et j’ai remarqué que la construction de GCC produisait beaucoup d’appels inutiles au constructeur de copie.

Voici un exemple de code pour générer ce comportement:

struct A { A() { std::cout << "default" << std::endl; } A(A&& rvalue) { std::cout << "move" << std::endl; } A(const A& lvalue) { std::cout << "copy" << std::endl; } A& operator =(A a) { std::cout << "assign" << std::endl; return *this; } }; BOOST_AUTO_TEST_CASE(test_copy_semantics) { std::vector vec_a( 3 ); } 

Ce test crée simplement un vecteur de 3 éléments. J’attends 3 appels de constructeur par défaut et 0 copie car il n’y a pas de valeurs A l.

Dans Visual C ++ 2010, le résultat est le suivant:

 default move default move default move 

Dans GCC 4.4.0 (MinGW), (-O2 -std = c ++ 0x), le résultat est le suivant:

 default copy copy copy 

Qu’est-ce qui se passe et comment puis-je résoudre ce problème? Les copies sont chères pour la classe actuelle, la construction par défaut et les déménagements sont bon marché.

Les deux implémentations (Visual C ++ 2010 et GCC 4.4.0) sont en erreur. La sortie correcte est:

 default default default 

Ceci est spécifié dans 23.3.5.1 [vector.cons] / 4:

Requiert: T doit être DefaultConstructible.

L’implémentation n’est pas autorisée à supposer que A est MoveConstructible ni CopyConstructible.

Le problème est que la version de g ++ que vous possédez ne possède pas de bibliothèque entièrement compatible C ++ 0x. En particulier, en C ++ 03, le constructeur de taille de std :: vector a la signature suivante:

 // C++ 03 explicit vector(size_type n, const T& value = T(), const Allocator& = Allocator()); 

Avec cette signature de fonction et votre appel, un temporaire est créé, puis lié à la référence constante et des copies de celle-ci sont créées pour chacun des éléments.

alors que dans C ++ 0x il y a différents constructeurs:

 // C++0x explicit vector(size_type n); vector(size_type n, const T& value, const Allocator& = Allocator()); 

Dans ce cas, votre appel correspondra à la première signature et les éléments devraient être construits par défaut avec un nouveau placement sur le conteneur (comme @Howard Hinnant le souligne correctement dans sa réponse, le compilateur ne devrait pas du tout appeler le constructeur de mouvements).

Vous pouvez essayer de vérifier si les versions les plus récentes de g ++ ont une bibliothèque standard mise à jour, ou vous pouvez contourner le problème en ajoutant manuellement les éléments:

 std::vector v; v.reserve( 3 ); // avoid multiple relocations while (v.size() < 3 ) v.push_back( A() ); 

Essayez ceci alors:

 std::vector vec_a; vec_a.reserve(3); for (size_t i = 0; i < 3; ++i) vec_a.push_back(A()); 

Ce que vous essayez de faire est de forcer le processus d'initialisation à utiliser construct + move pour chaque valeur au lieu de construct puis de copier / copier / copier. Ce ne sont que des philosophies différentes; les auteurs des bibliothèques ne pouvaient pas savoir lequel serait le meilleur pour un type donné.

Vous pouvez append un cas spécial (pas cher) pour copier l’algorithme ctor lors de la copie d’un object construit par défaut vers “cet” object. C’est juste une solution de contournement, cependant, le comportement est assez étrange. Les deux compilateurs (bibliothèques) créent un object temporaire sur la stack, puis gcc le copie 3 fois dans les cibles; msvc recrée l’object temporaire 3 fois (!) (sur la stack également) et se déplace 3 fois sur les cibles. Je ne comprends pas pourquoi ils ne créent pas d’objects directement sur place.

Je pense que les 3 variantes ne violent pas le brouillon C ++ 0x. Cela nécessite ce qui suit: 1. Construit un vecteur avec n éléments initialisés par une valeur 2. T doit être DefaultConstructible 3. Linear in n

Toutes les 3 variantes satisfont 1, par défaut + copie, par défaut + déplacer sont équivalentes à défaut. Les 3 variantes satisfont 3 Les 3 variantes satisfont 2: elles fonctionnent pour les types DefaultConstructible. Un algorithme spécifique peut être utilisé pour les types Moveable. En LIST, il est de pratique courante d’utiliser différentes versions d’algorithmes pour des types dotés de fonctionnalités différentes.