Supposons que j'ai cette fonction:
void my_test()
{
A a1 = A_factory_func();
A a2(A_factory_func());
double b1 = 0.5;
double b2(0.5);
A c1;
A c2 = A();
A c3(A());
}
Dans chaque groupe, ces déclarations sont-elles identiques? Ou existe-t-il une copie supplémentaire (éventuellement optimisable) dans certaines des initialisations?
J'ai vu des gens dire les deux choses. Veuillez citer le texte comme preuve. Ajoutez également d'autres cas, s'il vous plaît.
c++
initialization
rlbond
la source
la source
A c1; A c2 = c1; A c3(c1);
.A
, l'initialisation de la copie ne nécessiterait pas l'existence d'un constructeur de copie / déplacement. C'est pourquoistd::atomic<int> a = 1;
ça va en C ++ 17 mais pas avant.Réponses:
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 (commea1
), 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, dansA_factory_func();
. Dans le dernier cas, un objet est créé artificiellement, appelé "matérialisation temporaire", car ilA_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
eta2
dire que dans de telles déclarations, l'objet de résultat d'un initialiseur de valeur du même type quea1
variable esta1
, et doncA_factory_func()
initialise directement l'objeta1
. N'importe quelle distribution intermédiaire de style fonctionnel n'aurait aucun effet, parce queA_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.Cela dépend du type de
A_factory_func()
retour. Je suppose qu'il renvoie unA
- alors il fait de même - sauf que lorsque le constructeur de copie est explicite, le premier échouera. Lire 8.6 / 14Cela 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 .
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 dansc2
(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 fonctionc3
qui renvoie unA
et qui prend un pointeur de fonction vers une fonction renvoyant unA
(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:
Il y a un comportement que nous pouvons attribuer à chacun d'eux:
T
(y comprisexplicit
celles), et l'argument estx
. La résolution de surcharge trouvera le meilleur constructeur correspondant et, si nécessaire, effectuera toute conversion implicite requise.x
en un objet de typeT
. (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.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 :
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éeB
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 suivantsRemarquez 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 avecx
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!
la source
R() == R(*)()
etT[] == T*
. En d'autres termes, les types de fonction sont des types de pointeur de fonction et les types de tableau sont des types de pointeur vers élément. Ça craint. Il peut être contourné parA c3((A()));
(parens autour de l'expression).L'affectation est différente de l' initialisation .
Les deux lignes suivantes effectuent l' initialisation . Un seul appel constructeur est effectué:
mais ce n'est pas équivalent à:
Pour l'instant, je n'ai pas de texte pour le prouver mais c'est très simple à expérimenter:
la source
double b1 = 0.5;
est l'appel implicite du constructeur.double b2(0.5);
est un appel explicite.Regardez le code suivant pour voir la différence:
Si votre classe n'a pas de constucteurs explicites, les appels explicites et implicites sont identiques.
la source
Premier regroupement: cela dépend de ce qui
A_factory_func
revient. La première ligne est un exemple d' initialisation de copie , la deuxième ligne est l'initialisation directe . SiA_factory_func
renvoie unA
objet, alors ils sont équivalents, ils appellent tous les deux le constructeur de copie pourA
, sinon la première version crée une valeur r de type àA
partir des opérateurs de conversion disponibles pour le type de retour deA_factory_func
ou lesA
constructeurs appropriés , puis appelle le constructeur de copie pour construire àa1
partir de ce temporaire. La deuxième version tente de trouver un constructeur approprié qui accepte tout ce quiA_factory_func
retourne, ou qui prend quelque chose dans lequel la valeur de retour peut être implicitement convertie.Deuxième regroupement: exactement la même logique s'applique, sauf que les types intégrés n'ont pas de constructeurs exotiques, ils sont donc, en pratique, identiques.
Troisième regroupement:
c1
est initialisé par défaut,c2
est initialisé en copie à partir d'une valeur initialisée temporaire. Tous les membres dec1
ce type pod (ou membres de membres, etc., etc.) ne peuvent pas être initialisés si les constructeurs par défaut fournis par l'utilisateur (le cas échéant) ne les initialisent pas explicitement. Pourc2
, cela dépend de l'existence d'un constructeur de copie fourni par l'utilisateur et de l'initialisation appropriée de ces membres, mais les membres du temporaire seront tous initialisés (initialisés à zéro sinon autrement explicitement initialisés). Comme litb repéré,c3
est un piège. C'est en fait une déclaration de fonction.la source
À noter:
[12.2 / 1]
Temporaries of class type are created in various contexts: ... and in some initializations (8.5).
C'est-à-dire pour la copie-initialisation.
[12.8 / 15]
When certain criteria are met, an implementation is allowed to omit the copy construction of a class object ...
En d'autres termes, un bon compilateur ne créera pas de copie pour l'initialisation de copie quand cela peut être évité; au lieu de cela, il appellera simplement le constructeur directement - c'est-à-dire, tout comme pour l'initialisation directe.
En d'autres termes, la copie-initialisation est exactement comme l'initialisation directe dans la plupart des cas <opinion> où du code compréhensible a été écrit. Étant donné que l'initialisation directe provoque potentiellement des conversions arbitraires (et donc probablement inconnues), je préfère toujours utiliser l'initialisation par copie lorsque cela est possible. (Avec le bonus qu'il ressemble en fait à l'initialisation.) </opinion>
Sommité technique: [12,2 / 1 cont d'en haut]
Even when the creation of the temporary object is avoided (12.8), all the semantic restrictions must be respected as if the temporary object was created.
Heureux de ne pas écrire de compilateur C ++.
la source
Vous pouvez voir sa différence
explicit
et sesimplicit
types de constructeurs lorsque vous initialisez un objet:Des classes :
Et dans la
main
fonction:Par défaut, un constructeur est comme
implicit
vous avez donc deux façons de l'initialiser:Et en définissant une structure aussi
explicit
simplement, vous avez une façon aussi directe:la source
Réponse à l'égard de cette partie:
Comme la plupart des réponses sont pré-c ++ 11, j'ajoute ce que c ++ 11 a à dire à ce sujet:
Donc, optimisation ou non, ils sont équivalents selon la norme. Notez que cela est conforme à ce que d'autres réponses ont mentionné. Je cite simplement ce que la norme a à dire par souci d'exactitude.
la source
Beaucoup de ces cas sont soumis à l'implémentation d'un objet, il est donc difficile de vous donner une réponse concrète.
Considérez le cas
Dans ce cas, en supposant un opérateur d'affectation approprié et un constructeur d'initialisation qui acceptent un seul argument entier, la façon dont j'implémente lesdites méthodes affecte le comportement de chaque ligne. Cependant, il est courant que l'un de ceux-ci appelle l'autre dans la mise en œuvre pour éliminer le code en double (bien que dans un cas aussi simple que cela, il n'y aurait pas de véritable objectif).
Edit: Comme mentionné dans d'autres réponses, la première ligne appellera en fait le constructeur de copie. Considérez les commentaires relatifs à l'opérateur d'affectation comme un comportement se rapportant à une affectation autonome.
Cela dit, la façon dont le compilateur optimise le code aura alors son propre impact. Si j'ai le constructeur d'initialisation appelant l'opérateur "=" - si le compilateur ne fait aucune optimisation, la ligne du haut effectuerait alors 2 sauts par opposition à un dans la ligne du bas.
Maintenant, pour les situations les plus courantes, votre compilateur optimisera à travers ces cas et éliminera ce type d'inefficacité. Donc, effectivement, toutes les situations différentes que vous décrivez seront les mêmes. Si vous voulez voir exactement ce qui se fait, vous pouvez regarder le code objet ou une sortie d'assembly de votre compilateur.
la source
operator =(const int)
et nonA(const int)
. Voir la réponse de @ jia3ep pour plus de détails.Il s'agit du langage de programmation C ++ de Bjarne Stroustrup:
Une initialisation avec un = est considérée comme une initialisation de copie . En principe, une copie de l'initialiseur (l'objet à partir duquel nous copions) est placée dans l'objet initialisé. Cependant, une telle copie peut être optimisée (élidée) et une opération de déplacement (basée sur la sémantique de déplacement) peut être utilisée si l'initialiseur est une valeur r. Le fait de laisser le = rend l'initialisation explicite. L'initialisation explicite est appelée initialisation directe .
la source