Mise à jour C++17
En C++17, la signification de A_factory_func()
est passé de la création d'un objet temporaire (C++<=14) à la simple spécification de l'initialisation de l'objet sur lequel cette expression est initialisée (au sens large) en C++17. Ces objets (appelés "objets résultat") sont les variables créées par une déclaration (comme a1
), des objets artificiels créés lorsque l'initialisation finit par être abandonnée, ou si un objet est nécessaire pour la liaison de référence (comme dans le cas de A_factory_func();
. Dans le dernier cas, un objet est créé artificiellement, appelé "matérialisation temporaire", car A_factory_func()
n'a pas de variable ou de référence qui nécessiterait l'existence d'un objet).
A titre d'exemple dans notre cas, dans le cas de a1
y a2
Des règles spéciales stipulent que dans de telles déclarations, l'objet résultat d'un initialisateur prvalue du même type que a1
est variable a1
et donc A_factory_func()
initialise directement l'objet a1
. Toute coulée intermédiaire de style fonctionnel n'aurait aucun effet, car A_factory_func(another-prvalue)
ne fait que "traverser" l'objet résultat de la prvalue externe pour être également l'objet résultat de la prvalue interne.
A a1 = A_factory_func();
A a2(A_factory_func());
Cela dépend du type A_factory_func()
retours. Je suppose qu'il renvoie un A
- alors il fait la même chose - sauf que si le constructeur de la copie est explicite, alors le premier échouera. Lire 8.6/14
double b1 = 0.5;
double b2(0.5);
Il fait la même chose parce que c'est un type intégré (ce qui signifie qu'il ne s'agit pas d'un type de classe). 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 pas d'initialisation pour un POD (Read 8.6/9 ). La deuxième copie s'initialise : Value-initialise une valeur temporaire et copie ensuite cette valeur dans c2
(Lire 5.2.3/2 y 8.6/14 ). Cela nécessitera bien sûr un constructeur de copie non explicite (Read 8.6/14 y 12.3.1/3 y 13.3.1.3/1 ). La 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 retournant un A
(Lire 8.2 ).
Approfondir les initialisations Initialisation du direct et de la copie
Bien qu'elles aient l'air identiques et soient censées faire la même chose, ces deux formes sont remarquablement différentes dans certains cas. Les deux formes d'initialisation sont l'initialisation directe et l'initialisation par 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 à une fonction surchargée : Les fonctions, dans ce cas, sont les constructeurs de
T
(y compris explicit
), et l'argument est x
. La résolution de surcharge trouvera le constructeur le mieux adapté et, si nécessaire, effectuera toute conversion implicite requise.
- L'initialisation de la copie construit une séquence de conversion implicite : Elle essaie de convertir
x
à un objet de type T
. (Il peut ensuite copier cet objet dans l'objet à initialiser, donc un constructeur de copie est également nécessaire - mais ce n'est pas important ci-dessous).
Comme vous le voyez, initialisation de la copie fait en quelque sorte partie de l'initialisation directe en ce qui concerne les éventuelles conversions implicites : Alors que l'initialisation directe dispose de tous les constructeurs disponibles à appeler, et que en outre peut effectuer toutes les conversions implicites dont elle a besoin pour faire correspondre les types d'arguments, l'initialisation de la copie peut juste mettre en place une séquence de conversion implicite.
J'ai fait des efforts et a obtenu le code suivant pour sortir un texte différent pour chacun de ces formulaires sans recourir à l'"évidence" par 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 fonctionne-t-il, et pourquoi produit-il ce résultat ?
-
Initialisation directe
Il ne sait d'abord rien de la conversion. Il va juste essayer d'appeler un constructeur. Dans ce cas, le constructeur suivant est disponible et est un correspondance exacte :
B(A const&)
Aucune conversion, et encore moins une conversion définie par l'utilisateur, n'est 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 construira une séquence de conversion lorsque a
n'a pas de type B
ou dérivé de celui-ci (ce qui est clairement le cas ici). Il cherchera donc des moyens d'effectuer 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 du paramètre reflète le type de l'objet this
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 : Car si nous avons deux fonctions candidates acceptant toutes deux une référence au même type, alors la fonction moins constant l'emporte (c'est d'ailleurs aussi le mécanisme qui préfère les appels de fonctions membres non-const pour les objets non-const).
Notez que si nous changeons la fonction de conversion pour qu'elle soit une fonction membre const, alors la conversion est ambiguë (parce que les deux ont un type de paramètre de A const&
alors) : Le compilateur Comeau le rejette correctement, mais GCC l'accepte en mode non pédant. En passant à -pedantic
fait sortir l'avertissement d'ambiguïté approprié aussi, cependant.
J'espère que cela vous aidera à mieux comprendre la différence entre ces deux formes !
1 votes
Et il y a le quatrième cas discuté par @JohannesSchaub -
A c1; A c2 = c1; A c3(c1);
.2 votes
Juste une note pour 2018 : Les règles ont changé en C++17 voir, par exemple, aquí . Si ma compréhension est correcte, en C++17, les deux déclarations sont effectivement les mêmes (même si le copy ctor est explicite). De plus, si l'expression init est d'un autre type que
A
L'initialisation de la copie ne nécessite pas l'existence du constucteur copy/move. C'est pourquoistd::atomic<int> a = 1;
est acceptable en C++17 mais pas avant.