Pourquoi réaffecter une copie vectorielle au lieu de déplacer les éléments?

Dupliquer possible:
Comment appliquer la sémantique de déplacement quand un vecteur grandit?

insert , push_back et _back ( _back ) peuvent provoquer une réallocation d’un std::vector . J’étais perplexe de constater que le code suivant copie les éléments au lieu de les déplacer lors de la réaffectation du conteneur.

 #include  #include  struct foo { int value; explicit foo(int value) : value(value) { std::cout << "foo(" << value << ")\n"; } foo(foo const& other) noexcept : value(other.value) { std::cout << "foo(foo(" << value << "))\n"; } foo(foo&& other) noexcept : value(std::move(other.value)) { other.value = -1; std::cout << "foo(move(foo(" << value << "))\n"; } ~foo() { if (value != -1) std::cout << "~foo(" << value << ")\n"; } }; int main() { std::vector foos; foos.emplace_back(1); foos.emplace_back(2); } 

Sur mon ordinateur spécifique utilisant mon compilateur spécifique (GCC 4.7), cela affiche les éléments suivants:

 foo(1) foo(2) foo(foo(1)) ~foo(1) ~foo(1) ~foo(2) 

Cependant, lors de la suppression du constructeur de la copie ( foo(foo const&) = delete; ), la sortie suivante (attendue) est générée:

 foo(1) foo(2) foo(move(foo(1)) ~foo(1) ~foo(2) 

Pourquoi donc? Les déplacements ne seraient-ils généralement pas plus efficaces, ou du moins pas beaucoup moins efficaces, que la copie?

Il convient de noter que GCC 4.5.1 remplit les fonctions attendues : s’agit-il d’une régression dans GCC 4.7 ou s’agit-il d’une optimisation astucieuse, car le compilateur voit que mon object est économique à copier (mais comment?!)?

Notez également que je me suis assuré que cela est dû à la réaffectation, en mettant expérimentalement un object foos.reserve(2); devant les insertions; cela ne provoque ni copie ni déplacement à exécuter.

La réponse courte est que je pense que @BenVoigt est fondamentalement correct.

Dans la description de la reserve (§23.3.6.3 / 2), il est écrit:

Si une exception est levée autrement que par le constructeur de déplacement d’un type non CopyInsertable, il n’y a pas d’effet.

[Et la description de resize dans §23.3.6.3 / 12 exige la même chose.]

Cela signifie que si T est CopyInsertable, la sécurité des exceptions est forte. Pour garantir cela, il ne peut utiliser la construction de mouvements que s’il en déduit (par des moyens non spécifiés) que la construction de mouvements ne sera jamais lancée. Cependant, rien ne garantit que throw() ou noexcept sera nécessaire ou suffisant pour cela. Si T est CopyInsertable, il peut simplement choisir de toujours utiliser la construction de copie. Fondamentalement, ce qui se passe, c’est que la norme exige une sémantique semblable à la construction de copie. le compilateur ne peut utiliser la construction de déplacements que sous la règle as-if, et il est libre de définir quand ou s’il exercera cette option.

Si T n’est pas CopyInsertable, la réallocation utilisera la construction de déplacement, mais la sécurité des exceptions dépend du fait que le constructeur de déplacement de T puisse ou non lancer. Si cela ne se produit pas, vous bénéficiez d’une sécurité exceptionnelle, mais si vous le faites, vous ne le ferez pas (je pense que vous obtenez probablement la garantie de base, mais peut-être même pas cela et certainement plus).

Ce n’est pas une régression, c’est une correction de bogue. La norme spécifie que std :: vector préférera uniquement un constructeur de déplacement d’élément qui ne lance pas.

Voir aussi cette explication et ce rapport de bogue .

Cette question est également pertinente.

Tip-of-trunk clang + libc ++ obtient:

 foo(1) foo(2) foo(move(foo(1)) ~foo(2) ~foo(1) 

Si vous supprimez le noexcept du constructeur du déplacement, vous obtenez la solution de copie:

 foo(1) foo(2) foo(foo(1)) ~foo(1) ~foo(2) ~foo(1)