2014-07-17 1 views
2

J'ai ce morceau de code:std :: vecteur d'initialisation mouvement/constructeur de copie de l'élément

#include <iostream> 
#include <vector> 

using namespace std; 

class Foo{ 
public: 
    Foo() noexcept {cout << "ctor" << endl;} 
    Foo(const Foo&) noexcept {cout << "copy ctor" << endl;} 
    Foo(Foo&&) noexcept {cout << "move ctor" << endl;} 

    Foo& operator=(Foo&&) noexcept {cout << "move assn" << endl; return *this;} 
    Foo& operator=(const Foo&) noexcept {cout << "copy assn" << endl; return *this;} 

    ~Foo() noexcept {cout << "dtor" << endl;} 
}; 


int main() 
{ 
    Foo foo; 

    vector<Foo> v; 
    v.push_back(std::move(foo)); 

    // comment the above 2 lines and replace by 
    // vector<Foo> v{std::move(foo)}; 
} 

La sortie est ce que je pense (compilé avec g++ -std=c++11 --no-elide-constructors, même sortie sans le drapeau)

ctor 
move ctor 
dtor 
dtor 

maintenant au lieu d'utiliser directement push_back initialiser le vecteur v comme

vector<Foo> v{std::move(foo)}; 

Je ne comprends pas pourquoi je reçois les sorties:

1) (sans --no-elide-constructors)

ctor 
move ctor 
copy ctor 
dtor 
dtor 
dtor 

2) (avec --no-elide-constructors)

ctor 
move ctor 
move ctor 
copy ctor 
dtor 
dtor 
dtor 
dtor 

Dans le premier cas, pourquoi la Copier ctor invoqué? Et dans le second cas, quand le compilateur n'effectue pas d'élision, je n'ai absolument aucune idée de pourquoi le mouvement ctor est invoqué deux fois. Des idées?

Répondre

8
vector<Foo> v{std::move(foo)}; 

Vous êtes ici d'appeler le constructeur vecteur qui prend une std::initializer_list. Une liste d'initialisation n'autorise l'accès à ses éléments qu'à const. Le vector devra donc copier chaque élément du initializer_list dans son propre stockage. C'est ce qui provoque l'appel au constructeur de la copie.

De §8.5.4/5 [dcl.init.list]

Un objet de type std::initializer_list<E> est construit à partir d'une liste d'initialisation que si l'exécution a alloué un tableau temporaire de N éléments de tapez const E, où N est le nombre d'éléments dans la liste d'initialisation.


En ce qui concerne l'appel du constructeur de mouvement supplémentaire avec -fno-elide-constructors, cela a été discuté dans another answer il y a quelques jours. Il semble que g ++ adopte une approche très littérale de l'exemple de mise en œuvre d'un initializer_list montré dans la norme dans la même section que j'ai citée ci-dessus.

Le même exemple, lorsque compiled using clang, ne génère pas l'appel du constructeur de déplacement supplémentaire.

+0

Ohhh, j'ai compris ... La syntaxe '{...}' pour l'initialisation uniforme est assez compliquée, et j'ai maintenant réalisé que 'vector' n'est pas un agrégat. Et oui, même sur mon clang, pas de mouvement supplémentaire. Bonne réponse, merci! – vsoftco

+0

Je suis d'accord avec T.C .: le fait que gcc produise deux appels constructeurs de mouvement semble être un bug. Mais je suppose que tant que ce comportement n'est observable que lorsque vous passez '-fno-elide-constructors', il n'y a pas de problème. – Brian

+0

La réponse de T.C. le cloue, je suis d'accord que le comportement de clang semble correct. Le code suivant devrait être le même: 'Foo arr [1] = {std :: move (foo)}; vecteur v (arr, arr + 1); Le 'initializer_list' devrait seulement servir à maintenir' arr' et 'arr + 1', ne pas déplacer/copier les éléments. –

3

Les conteneurs essayent très difficile de s'assurer qu'ils restent utilisables en cas d'exception. Dans le cadre de cela, ils n'utiliseront std::move en interne que si le constructeur de déplacement de votre classe est en sécurité. Si ce n'est pas le cas, (ou il ne peut pas le dire), il va copier juste pour être sûr.

Les opérations de déplacement correctes sont

Foo(Foo&&) noexcept {cout << "move ctor" << endl;} 
Foo& operator=(Foo&&) noexcept {cout << "move assn" << endl;} 
+0

Merci, je l'ai changé, maintenant tous les cteurs (avec le 'opérateur =' ajouté) sont marqués comme 'noexcept', mais j'ai toujours exactement la même sortie (cas 1) et 2) ci-dessus). J'utilise 'g ++ 4.9' btw. – vsoftco

+0

@vsoftco: Le prétorien l'a compris, cliquez sur accepter dans sa réponse. –

Questions connexes