Mise à jour C ++ 17
En C ++ 17, la signification de A_factory_func()
changé de créer un objet temporaire (C ++ <= 14) à simplement spécifier l'initialisation de tout objet auquel cette expression est initialisée (en gros) en C ++ 17. Ces objets (appelés "objets de résultat") sont les variables créées par une déclaration (comme a1
), les objets artificiels créés lorsque l'initialisation finit par être rejetée, ou si un objet est nécessaire pour la liaison de référence (comme, dans A_factory_func();
. Dans le dernier cas, un objet est créé artificiellement, appelé "matérialisation temporaire", car il A_factory_func()
n'a pas de variable ou de référence qui, autrement, exigerait qu'un objet existe).
Comme exemples dans notre cas, dans le cas de règles spéciales a1
et a2
dire que dans de telles déclarations, l'objet de résultat d'un initialiseur de valeur du même type que a1
variable est a1
, et donc A_factory_func()
initialise directement l'objet a1
. N'importe quelle distribution intermédiaire de style fonctionnel n'aurait aucun effet, parce que A_factory_func(another-prvalue)
simplement "passe" l'objet résultat de la valeur externe pour être également l'objet résultat de la valeur interne.
A a1 = A_factory_func();
A a2(A_factory_func());
Cela dépend du type de A_factory_func()
retour. Je suppose qu'il renvoie un A
- alors il fait de même - sauf que lorsque le constructeur de copie est explicite, le premier échouera. Lire 8.6 / 14
double b1 = 0.5;
double b2(0.5);
Cela fait la même chose car c'est un type intégré (cela ne signifie pas un type de classe ici). Lire 8.6 / 14 .
A c1;
A c2 = A();
A c3(A());
Ce n'est pas la même chose. Le premier initialise par défaut si A
est un non-POD, et ne fait aucune initialisation pour un POD (lire 8.6 / 9 ). La deuxième copie initialise: Value initialise un temporaire puis copie cette valeur dans c2
(Lire 5.2.3 / 2 et 8.6 / 14 ). Cela nécessitera bien sûr un constructeur de copie non explicite (Lire 8.6 / 14 et 12.3.1 / 3 et 13.3.1.3/1 ). Le troisième crée une déclaration de fonction pour une fonction c3
qui renvoie un A
et qui prend un pointeur de fonction vers une fonction renvoyant un A
(Lire 8.2 ).
Plonger dans les initialisations directes et copier l'initialisation
Bien qu'ils semblent identiques et censés faire de même, ces deux formes sont remarquablement différentes dans certains cas. Les deux formes d'initialisation sont l'initialisation directe et la copie:
T t(x);
T t = x;
Il y a un comportement que nous pouvons attribuer à chacun d'eux:
- L'initialisation directe se comporte comme un appel de fonction à une fonction surchargée: les fonctions, dans ce cas, sont les constructeurs de
T
(y compris explicit
celles), et l'argument est x
. La résolution de surcharge trouvera le meilleur constructeur correspondant et, si nécessaire, effectuera toute conversion implicite requise.
- L'initialisation de copie construit une séquence de conversion implicite: elle essaie de se convertir
x
en un objet de type T
. (Il peut ensuite copier cet objet dans l'objet initialisé, donc un constructeur de copie est également nécessaire - mais ce n'est pas important ci-dessous)
Comme vous le voyez, l' initialisation de la copie fait en quelque sorte partie de l'initialisation directe en ce qui concerne les conversions implicites possibles: alors que l'initialisation directe a tous les constructeurs disponibles pour appeler, et en plus peut effectuer toute conversion implicite dont elle a besoin pour faire correspondre les types d'arguments, copier l'initialisation peut simplement configurer une séquence de conversion implicite.
J'ai essayé dur et j'ai obtenu le code suivant pour sortir un texte différent pour chacun de ces formulaires , sans utiliser le "évident" via les explicit
constructeurs.
#include <iostream>
struct B;
struct A {
operator B();
};
struct B {
B() { }
B(A const&) { std::cout << "<direct> "; }
};
A::operator B() { std::cout << "<copy> "; return B(); }
int main() {
A a;
B b1(a); // 1)
B b2 = a; // 2)
}
// output: <direct> <copy>
Comment cela fonctionne-t-il et pourquoi génère-t-il ce résultat?
Initialisation directe
Tout d'abord, il ne sait rien de la conversion. Il va juste essayer d'appeler un constructeur. Dans ce cas, le constructeur suivant est disponible et correspond exactement :
B(A const&)
Il n'y a pas de conversion, encore moins une conversion définie par l'utilisateur, nécessaire pour appeler ce constructeur (notez qu'aucune conversion de qualification const ne se produit ici non plus). Et donc l'initialisation directe l'appellera.
Initialisation de la copie
Comme indiqué ci-dessus, l'initialisation de la copie va construire une séquence de conversion lorsqu'elle a
n'a pas été typée B
ou dérivée de celle-ci (ce qui est clairement le cas ici). Il cherchera donc des moyens de faire la conversion et trouvera les candidats suivants
B(A const&)
operator B(A&);
Remarquez comment j'ai réécrit la fonction de conversion: Le type de paramètre reflète le type du this
pointeur, qui dans une fonction membre non const est à non-const. Maintenant, nous appelons ces candidats avec x
comme argument. Le gagnant est la fonction de conversion: parce que si nous avons deux fonctions candidates acceptant toutes les deux une référence au même type, alors la version moins const l' emporte (c'est d'ailleurs le mécanisme qui préfère la fonction membre non const appelle non -const objets).
Notez que si nous changeons la fonction de conversion pour être une fonction membre const, alors la conversion est ambiguë (car les deux ont un type de paramètre A const&
alors): le compilateur Comeau la rejette correctement, mais GCC l'accepte en mode non pédant. -pedantic
Cependant, le basculement vers le produit génère également l'avertissement d'ambiguïté approprié.
J'espère que cela aide un peu à rendre plus claire la différence entre ces deux formes!
A c1; A c2 = c1; A c3(c1);
.