C++: le retour par référence et copie des constructeurs
Les références en C++ sont déconcertants moi. 🙂
L'idée de base est que je suis en train de retourner un objet à partir d'une fonction. J'aimerais le faire sans avoir à retourner un pointeur (parce que j'en serais manuellement delete
elle), et sans appeler le constructeur par copie, si possible (pour plus d'efficacité, naturellement ajouté: et aussi parce que je me demande si je ne peux pas éviter d'écrire un constructeur de copie).
Donc, dans l'ensemble, voici les options que j'ai trouvé:
- Le type de retour de fonction peut être soit la classe elle-même (
MyClass fun() { ... }
) ou une référence à la classe (MyClass& fun() { ... }
). - La fonction peut se construire la variable à la ligne de retour (
return MyClass(a,b,c);
) ou retourner une variable existante (MyClass x(a,b,c); return x;
). - Le code qui reçoit la variable peut également avoir une variable de type: (
MyClass x = fun();
ouMyClass& x = fun();
) - Le code qui reçoit la variable pouvez soit créer une nouvelle variable à la volée (
MyClass x = fun();
) ou l'affecter à une variable existante (MyClass x; x = fun();
)
Et quelques réflexions sur que:
- Il semble être une mauvaise idée d'avoir le type de retour
MyClass&
parce que c'est toujours la variable d'être détruit avant qu'il soit retourné. - Le constructeur de copie ne semble s'impliquer quand je retourner une variable existante. Lors du retour d'une variable construite dans la ligne de retour, elle n'est jamais appelée.
- Quand j'affecter le résultat à une variable existante, le destructeur aussi toujours des coups de pied en avant la valeur est retournée. Aussi, pas de constructeur de copie est appelé, encore variable cible reçoit les valeurs d'un membre de l'objet retourné par la fonction.
Ces résultats sont si incompatibles que je me sens totalement confus. Alors, quelle est EXACTEMENT ce qui se passe ici? Comment dois-je correctement construire et retourne un objet à partir d'une fonction?
- La raison pour laquelle le constructeur de copie n'est pas d'être appelé dans le second cas est appelé retour de la valeur de l'optimisation, ou de la RVO. Le compilateur est permis d'omettre le temporaire + copie, même si cela change le comportement du programme.
- Ce sur le retour à une variable existante problème? J'ai été confronté, mais je ne pouvais pas comprendre pourquoi le destructeur des coups de pied dans un pas de constructeur de copie est appelé. Avez-vous jamais savoir ce qui en est la cause?
- Désolé, non, je n'ai pas beaucoup travaillé avec C++ et je ne sais pas la réponse. Je pense que j'ai abandonné ce projet ou réécrit différemment. (c'était il y a 5 ans, vous savez).
Vous devez vous connecter pour publier un commentaire.
La meilleure façon de comprendre la copie en C++ est souvent de ne PAS essayer de produire un exemple artificiel et instrument - le compilateur est autorisé à la fois supprimer et ajouter constructeur de copie d'appels, plus ou moins comme il l'entend.
Bas de ligne - si vous avez besoin de retourner une valeur, retourner une valeur et ne vous inquiétez pas à propos de tout "frais".
move
sémantique. Au lieu de faire un copier puis de perdre l'un de transférer un objet, il vous suffit de déplacer les données internes; il n'y a jamais existe plus d'une copie.Lecture recommandée: Effective C++ par Scott Meyers. Vous trouverez une très bonne explication sur ce sujet (et beaucoup plus) dans y.
En bref, si vous revenez en valeur, le constructeur de copie et le destructeur sera impliqué par défaut (à moins que le compilateur optimise loin - qu'est ce qui se passe dans certains de vos affaires).
Si vous revenez par référence (ou un pointeur) une variable est locale (construit sur la pile), vous inviter en difficulté parce que l'objet est détruit lors de la restitution, de sorte que vous avez une balançant de référence comme un résultat.
La manière canonique de construire un objet à une fonction et le retour c'est par la valeur, comme:
Si vous utilisez cette option, vous n'avez pas besoin de s'inquiéter à propos de questions liées à la propriété, balançant des références, etc. Et le compilateur le plus susceptible d'optimiser la copie supplémentaire constructeur /destructeur appelle pour vous, donc vous n'avez pas besoin de vous soucier de la performance.
Il est possible de revenir par la référence à un objet construit par
new
(c'est à dire sur le tas) - cet objet ne sera pas détruit lors de son retour de la fonction. Cependant, vous devez détruire explicitement quelque part plus tard en appelantdelete
.Il est techniquement possible de stocker un objet retourné par la valeur de référence, comme:
Cependant, autant que je sache, il n'y a pas beaucoup de point en faisant cela. Surtout parce que l'on peut facilement passer à cette référence à d'autres parties du programme qui sont en dehors de la portée actuelle; toutefois, l'objet référencé par
x
est un objet local qui sera détruit dès que vous quittez le champ d'application actuel. Donc, ce style peut conduire à de méchants bugs.lire sur RVO et NRVO (en un mot, ces deux supports pour Valeur de Retour de l'Optimisation et Nommé RVO, et sont des techniques d'optimisation utilisé par le compilateur pour faire ce que vous essayez d'atteindre)
vous trouverez un grand nombre de sujets ici sur stackoverflow
Si vous créez un objet comme ceci:
alors il sera sur la pile dans la fonction de cadre. Lorsque la fonction se termine, son cadre est sauté hors de la pile et de tous les objets dans ce cadre sont détruits. Il n'existe aucun moyen d'éviter cela.
Donc, si vous voulez retourner un objet à un appel, vous seules options sont:
Les tentatives de construction d'un objet local et ensuite renvoyer une référence à la mémoire locale à un appel en contexte n'est pas cohérent - un appel de marge peut pas accéder à la mémoire de ce qui est local à la appelé la portée. Que la mémoire locale est seulement valide pour la durée de la fonction à laquelle il appartient - ou, d'une autre manière, pendant l'exécution reste dans cette portée. Vous devez comprendre ceci afin de programmer en C++.
Sur le seul temps qu'il fait sens pour renvoyer une référence, si vous êtes de retour d'une référence à un objet préexistant. Pour un exemple évident, presque tous les iostream membre de la fonction renvoie une référence à l'iostream. Le iostream existe avant tout des fonctions de membre est appelé, et continue d'exister une fois qu'ils sont appelés.
La norme permet de "copier élision", ce qui signifie que le constructeur de copie n'a pas besoin d'être appelé lorsque vous revenez d'un objet. Cela vient sous deux formes: Nom de la Valeur de Retour d'Optimisation (NRVO) et anonyme Valeur de Retour d'Optimisation (généralement juste RVO).
De ce que vous dites, votre compilateur met en œuvre RVO mais pas NRVO -- ce qui signifie qu'il est probablement un peu plus de compilateur. La plupart des compilateurs gcc pour mettre en œuvre les deux. La non-correspondance dtor dans ce cas signifie qu'il est probablement quelque chose comme gcc 3.4 ou à peu près-mais je ne me souviens pas de la version pour sûr, il y a une autour de l'époque qui avait un bug de ce genre. Bien sûr, il est également possible que votre instrumentation n'est pas tout à fait le droit, donc un ctor que vous n'avez pas d'instrument est utilisé, et un correspondant de dtor est invoquée pour cet objet.
En fin de compte, vous êtes coincé avec un simple fait: si vous avez besoin de retourner un objet, vous avez besoin de retourner un objet. En particulier, une référence ne peut donner accès à une (peut-être une version modifiée d'un objet, mais cet objet a dû être construit à un certain point. Si vous pouvez modifier un objet existant sans causer un problème, que du beau et du bien, aller de l'avant et de le faire. Si vous avez besoin d'un nouvel objet différent et distinct de ceux que vous avez déjà, aller de l'avant et le faire-avant la création de l'objet et en le passant dans une référence à elle peut rendre le retour plus rapide, mais ne sauvera pas tout le temps ensemble. La création de l'objet a à peu près le même coût qu'il soit fait à l'intérieur ou à l'extérieur de la fonction. Tout raisonnablement moderne compilateur d'inclure RVO, donc vous n'aurez pas à payer de frais supplémentaires pour la création dans la fonction, puis en la retournant, le compilateur va juste automatiser l'allocation d'espace pour l'objet sur lequel il va être renvoyé, et ont pour fonction de construire "à la place", où il va encore être accessibles après la fonction renvoie.
Fondamentalement, en retournant une référence n'a de sens que si l'objet existe toujours après la sortie de la méthode. Le compilateur vous avertira si vous vous renvoyer une référence à quelque chose qui est en train d'être détruit.
Renvoie une référence plutôt que d'un objet par valeur enregistre la copie de l'objet qui pourrait être important.
Références sont plus sûrs que des pointeurs parce qu'ils ont différents symantics, mais dans les coulisses, ils sont des pointeurs.
int& f(bool f, int& x) { int y = 0; return f ? x : y; } int main() { int x; int& i = f(false, x); }
.Une solution potentielle, en fonction de votre cas d'utilisation, est à défaut de construire l'objet en dehors de la fonction, prendre dans une référence à elle, et d'initialiser l'objet référencé dans la fonction, comme suit:
Maintenant, bien sûr, cela ne fonctionne pas s'il n'est pas possible (ou n'a pas de sens) à défaut de construire un
Foo
objet puis l'initialiser plus tard. Si c'est le cas, alors votre seule option pour éviter la copie de la construction est de retourner un pointeur vers un tas objet alloué.Mais alors penser pourquoi vous essayez d'éviter le copier-construction dans la première place. Est la "charge" de la copie de la construction vraiment affecter votre programme, ou est-ce un cas de l'optimisation prématurée?
Vous êtes coincé avec soit:
1) retournant un pointeur
Maclasse* func(){
//certains stuf
de retour de new MyClass(a,b,c);
}
2) de retour d'une copie de l'objet
MyClass func(){
retour MyClass(a,b,c);
}
De retourner une référence n'est pas valide parce que l'objet est détruit après la sortie de la touche func portée, sauf si la fonction est un membre de la classe et la référence est à partir d'une variable membre de la classe.
Pas une réponse directe, mais une solution viable suggestion: Vous pouvez également retourner un pointeur, enveloppé dans un auto_ptr ou smart_ptr. Ensuite, vous serez en contrôle de ce que les constructeurs et les destructeurs d'être appelé et quand.