Question

Les références en C ++ sont me déconcertant. :)

L'idée de base est que je suis en train de retourner un objet à partir d'une fonction. Je voudrais le faire sans retourner un pointeur (car alors je dois delete manuellement), et sans faire appel à la copie-constructeur, si possible (pour l'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 pour ce faire que j'ai trouvé:

  • Le type de retour de la fonction peut être soit la même classe (MyClass fun() { ... }) ou une référence à la classe (MyClass& fun() { ... }).
  • La fonction peut soit 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 chaque type: (MyClass x = fun(); ou MyClass& x = fun();)
  • Le code qui reçoit la variable peut soit créer une nouvelle variable à la volée (MyClass x = fun();) ou l'assigner à une variable existante (MyClass x; x = fun();)

Et quelques réflexions sur ce qui suit:

  • Il semble être une mauvaise idée d'avoir le type de retour MyClass& parce que les résultats toujours dans la variable qui est détruite avant qu'il ne soit renvoyé.
  • Le constructeur de copie ne semble se impliquer quand je retourne une variable existante. En cas de retour d'une variable construite dans la ligne de retour, il ne sera jamais appelé.
  • Quand j'affecter le résultat à une variable existante, la destructor aussi débute toujours en avant la valeur est renvoyée. En outre, aucun constructeur de copie est appelée, mais la variable cible ne reçoit les valeurs membres de l'objet retourné par la fonction.

Ces résultats sont si contradictoires que je me sens tout à fait confus. Alors, qu'est-ce qui se passe ici EXACTEMENT? Comment dois-je construire correctement et renvoyer un objet à partir d'une fonction?

Était-ce utile?

La solution

La meilleure façon de comprendre la copie en C ++ est souvent d'essayer de produire un exemple artificiel et instrument, il - le compilateur est autorisé à la fois supprimer et ajouter des appels constructeur de copie, plus ou moins comme il l'entend.

Bottom line -. Si vous avez besoin de retourner une valeur, retourner une valeur et ne vous inquiétez pas une « dépense »

Autres conseils

Lecture recommandée: efficace C ++ par Scott Meyers. Vous trouverez une très bonne explication à ce sujet (et beaucoup plus) là-bas.

En bref, si vous revenez en valeur, le constructeur de copie et la destructor seront impliqués par défaut (à moins que le compilateur les optimise loin - c'est ce qui se passe dans certains de vos cas).

Si vous retournez par référence (ou pointeur) une variable qui est locale (construite sur la pile), vous invite du mal parce que l'objet est détruit au retour, si vous avez une référence boiteuse en conséquence.

La forme canonique pour construire un objet dans une fonction et le retourner est par valeur, comme:

MyClass fun() {
    return MyClass(a, b, c);
}

MyClass x = fun();

Si vous utilisez, vous n'avez pas à vous soucier des problèmes de propriété, des références, etc. ballants et le compilateur le plus probablement d'optimiser les appels constructeur copie supplémentaire / destructor pour vous, afin que vous n'avez pas à vous soucier de la performance soit.

Il est possible de revenir à titre de référence un objet construit par new (à savoir sur le tas) - cet objet ne sera pas détruit au retour de la fonction. Cependant, vous devez détruire explicitement quelque part plus tard en appelant delete.

Il est techniquement possible de stocker un objet retourné par la valeur dans une référence, comme:

MyClass& x = fun();

Cependant, il n'y a pas autant que je sache beaucoup d'intérêt à le faire. D'autant plus que l'on peut facilement transmettre cette référence à d'autres parties du programme qui sont en dehors du champ d'application actuel; cependant, l'objet référencé par x est un objet local qui sera détruit dès que vous quittez la portée actuelle. Donc, ce style peut conduire à des bugs désagréables.

lire RVO et NRVO (en un mot ces deux représente la valeur de retour Optimisation et nommé RVO, et sont des techniques d'optimisation utilisées par le compilateur pour faire ce que vous essayez d'atteindre)

vous trouverez beaucoup de sujets ici sur stackoverflow

Si vous créez un objet comme celui-ci:

MyClass foo(a, b, c);

alors il sera sur la pile dans le cadre de la fonction. Lorsque cette fonction se termine, son cadre est relevé de la pile et tous les objets de cette trame sont détruites. Il n'y a aucun moyen d'éviter cela.

Donc, si vous voulez retourner un objet à un appelant, vous ne options sont:

  • Retour en valeur -. Un constructeur de copie est nécessaire (mais l'appel au constructeur de copie peut être optimisé sur)
  • Retourne un pointeur et assurez-vous que vous pouvez soit utiliser des pointeurs intelligents pour traiter ou supprimer soigneusement vous-même lorsque vous avez terminé avec elle.

Tenter de construire un objet local, puis renvoyer une référence à cette mémoire locale à un contexte d'appel n'est pas cohérent - un champ d'appel ne peut pas accéder à la mémoire locale qui est à la portée appelée. Cette mémoire locale est uniquement valable pour la durée de la fonction qui est le propriétaire - ou, d'une autre manière, tandis que l'exécution reste dans ce champ d'application. Vous devez comprendre à programmer en C ++.

A propos de la seule fois où il est logique de renvoyer une référence est si vous retournez une référence à un objet préexistant. Pour un exemple évident, presque toutes les fonctions de membre iostream renvoie une référence à la iostream. Le iostream lui-même existe avant l'une des fonctions membres est appelé, et continue d'exister après qu'on les appelle.

La norme permet « copie élision », ce qui signifie que le constructeur de copie n'a pas besoin d'être appelé lorsque vous retournez un objet. Cela se présente sous deux formes:. Nom Valeur de retour Optimisation (NRVO) et anonyme Valeur de retour d'optimisation (habituellement juste RVO)

D'après ce que vous dites, votre compilateur implémente RVO mais pas NRVO - ce qui signifie qu'il est probablement un compilateur un peu plus. La plupart des compilateurs actuels mettent en œuvre à la fois. Le dtor de non-correspondance dans ce cas signifie qu'il est probablement quelque chose comme gcc 3.4 ou à peu près - si je ne me rappelle pas la version pour sûr, il y avait un autour alors qui avait un bug comme celui-ci. Bien sûr, il est également possible que votre instrumentation n'est pas tout à fait raison, donc un cteur que vous ne l'avez pas l'instrument est utilisé, et un dtor correspondant est invoquée pour cet objet.

En fin de compte, vous êtes coincé avec un simple fait que: si vous avez besoin de retourner un objet, vous devez renvoyer un objet. En particulier, une référence ne peut donner accès à une (version éventuellement modifiée) un objet existant - mais cet objet a dû être construit à un moment aussi. Si vous pouvez modifier un objet existant sans causer un problème, c'est bien et bien, allez-y 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 le passage dans une référence à elle peut se faire le retour plus rapide, mais ne de gagner du temps global. Création de l'objet a peu près le même coût que ce soit fait à l'intérieur ou à l'extérieur de la fonction. Tout compilateur raisonnablement moderne comprendra RVO, de sorte que vous ne payer aucun frais supplémentaire pour créer dans la fonction, puis le renvoyer - le compilateur simplement automatiser l'allocation d'espace pour l'objet où il va être de retour, et ont la fonction construire « en place », où il sera toujours accessible après le retour de la fonction.

En fait, le retour d'une référence n'a de sens que si l'objet existe toujours après avoir quitté la méthode. Le compilateur vous avertit si vous renvoie une référence à quelque chose qui est détruit.

De retour d'une référence plutôt que d'un objet par valeur copie de l'objet permet d'économiser ce qui pourrait être important.

Les références sont plus sûrs que les pointeurs parce qu'ils ont des symantics, mais dans les coulisses, ils sont des pointeurs.

Une solution possible, en fonction de votre cas d'utilisation, est par défaut-construire l'objet en dehors de la fonction, prendre une référence à elle, et initialiser l'objet référencé dans la fonction, comme si :

void initFoo(Foo& foo) 
{
  foo.setN(3);
  foo.setBar("bar");
  // ... etc ...
}

int main() 
{
  Foo foo;
  initFoo(foo);

  return 0;
}

Bien sûr, cela ne fonctionne pas s'il est impossible (ou n'a pas de sens) par défaut, construire un objet Foo puis l'initialiser plus tard. Si tel est le cas, votre seule option pour éviter la copie construction est de renvoyer un pointeur vers un objet alloué tas.

Mais pensez à pourquoi vous essayez d'éviter la construction de copie en premier lieu. Est-ce la « dépense » de construction de copie vraiment affecter votre programme, ou est-ce un cas d'optimisation prématurée?

Vous stucked soit:

1) renvoyant un pointeur

MyClass * func () {   // certains Stuf   retourner new MyClass (a, b, c); }

2) renvoyant une copie de l'objet MyClass func () {   retourner MyClass (a, b, c); }

De retour une référence est pas valable parce que l'objet doit être détruit après la sortie du périmètre de Func, sauf si la fonction est un membre de la classe et la référence est d'une variable qui est membre de la classe.

Pas une réponse directe, mais une suggestion viable: Vous pouvez également retourner un pointeur, enveloppé dans un auto_ptr ou smart_ptr. vous serez alors en contrôle de ce que les constructeurs et destructeurs s'appellent et quand.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top