Le compilateur doit se comporter comme si la copie est passée du vector
à l'appel Foo
.
Si le compilateur peut prouver qu'il ya est un comportement machine abstraite valide sans effets secondaires observables (dans le comportement de la machine abstraite, pas dans un véritable ordinateur!) Qui consiste à déplacer le std::vector
dans Foo
, il peut le faire.
Dans votre cas ci-dessus, ce mouvement n'a aucun effet secondaire visible sur la machine abstraite. le compilateur peut ne pas être capable de le prouver, cependant.
Le comportement observable éventuellement lors de la copie d'un std::vector<T>
est:
- invocation de constructeur de copie sur les éléments. Cela ne peut pas être observé avec
int
- Appel de la valeur par défaut
std::allocator<>
à différents moments. Cela appelle ::new
et ::delete
(peut-être) Dans tous les cas, ::new
et ::delete
n'a pas été remplacé dans le programme ci-dessus, donc vous ne pouvez pas observer cela sous la norme. Appeler le destructeur de T
plus de fois sur différents objets. Non observable avec int
.
- Le
vector
étant non vide après l'appel à Foo
. Personne ne l'examine, donc c'est vide comme si-ce n'était pas le cas.
- Références ou pointeurs ou itérateurs aux éléments du vecteur extérieur étant différents de ceux à l'intérieur. Aucune référence, vecteur ou pointeur n'est pris aux éléments du vecteur à l'extérieur
Foo
.
Alors que vous pouvez dire « mais si le système est hors de la mémoire, et le vecteur est grand, est pas observable? »:
La machine abstraite n'a pas « de mémoire "condition, il a simplement l'allocation échouant parfois (lancer std::bad_alloc
) pour des raisons non-contraintes. pas échec est un comportement valide de la machine abstraite, et ne pas manquer en n'allouant pas de mémoire (réelle) (sur l'ordinateur réel) est également valide, tant que la non-existence de la mémoire n'a pas d'effets secondaires observables.
Un peu plus cas de jouets:
int main() {
int* x = new int[std::size_t(-1)];
delete[] x;
}
alors que ce programme attribue clairement trop de mémoire, le compilateur est libre de ne pas affecter quoi que ce soit.
Nous pouvons aller plus loin. Même:
int main() {
int* x = new int[std::size_t(-1)];
x[std::size_t(-2)] = 2;
std::cout << x[std::size_t(-2)] << '\n';
delete[] x;
}
peut être transformé en std::cout << 2 << '\n';
. Ce grand tampon doit exister de façon abstraite, mais tant que votre "vrai" programme se comporte comme si la machine abstraite le faisait, il ne doit pas réellement l'allouer. Malheureusement, il est difficile de le faire à toute échelle raisonnable. Il y a beaucoup de façons dont les informations peuvent fuir à partir d'un programme C++. Donc, compter sur de telles optimisations (même si elles se produisent) ne va pas bien se terminer.
Il y avait des trucs sur coalescent appels à new
qui pourraient embrouiller la question, je ne suis pas certain si ce serait légal de passer des appels même s'il y avait un ::new
remplacé.
Un fait important est qu'il ya des situations que le compilateur est pas nécessaire de se comporter comme s'il y avait-une copie, même si std::move
n'a pas été appelé.
Lorsque vous return
une variable locale à partir d'une fonction dans une ligne qui ressemble return X;
et X
est l'identifiant, et en ce que la variable locale est d'une durée de stockage automatique (sur la pile), l'opération est implicitement un mouvement, et la compilateur (si possible) peut élider l'existence de la valeur de retour et la variable locale dans un objet (et même omettre le move
).
La même chose est vraie lorsque vous construisez un objet à partir d'un temporaire - l'opération est implicitement un mouvement (car il se lie à une valeur) et il peut se débarrasser complètement du mouvement. Dans les deux cas, le compilateur doit le traiter comme un mouvement (et non comme une copie), et il peut éviter le déplacement.
std::vector<int> foo() {
std::vector<int> x = {1,2,3,4};
return x;
}
que x
n'a pas std::move
, mais il est déplacé dans la valeur de retour, et cette opération peut être élidée (x
et la valeur de retour peut être transformé en un objet).
Ce:
std::vector<int> foo() {
std::vector<int> x = {1,2,3,4};
return std::move(x);
}
blocs élision, tout comme ceci:
std::vector<int> foo(std::vector<int> x) {
return x;
}
et nous pouvons même bloquer le mouvement:
std::vector<int> foo() {
std::vector<int> x = {1,2,3,4};
return (std::vector<int> const&)x;
}
ou même:
std::vector<int> foo() {
std::vector<int> x = {1,2,3,4};
return 0,x;
}
car les règles de déplacement implicite sont intentionnellement fragiles. (0,x
est une utilisation de l'opérateur ,
tant décrié). Maintenant, se fier au déplacement implicite qui ne se produit pas dans ce cas ,
n'est pas conseillé: le comité standard a déjà changé un cas de copie implicite en un déplacement implicite puisque le déplacement implicite a été ajouté au langage car ils l'ont jugé inoffensif (où la fonction renvoie un type A
avec un ctor A(B&&)
, et l'instruction de retour est return b;
où b
est de type B
; à la version C++ 11 qui a fait une copie, maintenant il fait un mouvement.) L'expansion d'implicit-move ne peut pas être exclue: le fait de lancer explicitement un const&
est probablement le moyen le plus fiable de l'empêcher maintenant et dans le futur.
Avez-vous des liens où je peux en apprendre plus sur comment cela fonctionne ou comment la machine abstraite est définie? – vu1p3n0x
@ vu1p3n0x Trouver une copie de la norme C++? [En voici un brouillon] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3690.pdf). [En voici une autre] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3376.pdf). Le comportement de C++ est spécifié dans la norme en termes de machine abstraite. – Yakk
Réponse parfaite. –