11
votes

Objet C ++ en tant que valeur de retour: copie ou référence?

Je voulais tester comment C ++ se comporte lorsque la valeur de retour d'une fonction est un objet. J'ai fait ce petit exemple pour regarder combien d'octets sont alloués et déterminent si le compilateur fait une copie de l'objet (comme lorsque l'objet est passé en tant que paramètre) ou renvoie plutôt une sorte de référence.

Cependant, je ne pouvais pas courir ce programme très simple et je n'ai aucune idée de pourquoi. Erreur dit: "L'assertion de débogage a échoué! Expression: block_type_is_invalid" dans un fichier dbgdel.cpp. Le projet est une application de console Win32. Mais je suis à peu près sûr qu'il y a quelque chose qui ne va pas avec ce code. xxx


3 commentaires

Dupliqué possible de [L'affirmation de débogage a échoué ... _block_type_is_valid (pheead] ( Stackoverflow.com/Questtions/1102123/... )


Le problème est que lorsque vous retournez la valeur, qui copie le pointeur de l'objet. Ensuite, les deux copies sont détruites, mais elles pointent tous les deux au même bloc de la mémoire. Lorsque la seconde est détruite, il essaie de réexprimer le même bloc de mémoire, ce qui n'est pas autorisé.


J'ai trouvé un joli page où il explique comment retourner des objets en C ++


4 Réponses :


15
votes

Vous avez violé le règle de trois .

Plus précisément, quand vous Renvoie un objet, une copie est fabriquée, puis détruite. Donc, vous avez une séquence d'événements comme xxx

qui est deux objets sont créés: votre construction d'objet d'origine, suivie du constructeur de copie implicite. Ensuite, ces deux objets sont supprimés.

Étant donné que ces deux objets contiennent le pointeur même , vous finissez par appeler Supprimer deux fois sur la même valeur. boom


crédit supplémentaire

: Quand j'échete des problèmes comme "Je me demande comment les copies sont faites", J'ai mis des déclarations d'impression dans les méthodes de classe intéressantes, comme ceci: xxx


7 commentaires

Bien que vous ayez raison, cette réponse va totalement de sens sans signification à moins que l'OP ne sait déjà ce que cela signifie. Pouvez-vous s'il vous plaît être plus descriptif?


Vous m'avez attrapé au milieu de l'artisanat de la réponse. C'est fini maintenant.


WOW Merci gars, jamais entendu parler de la règle de trois, je savais que la copie de l'objet avec le pointeur pourrait en résulter, mais je ne savais pas que le retour d'expression peut appeler destructeurs de quelque chose.


Pourquoi retourne-t-il en copie et le détruit immédiatement? :] Je ne peux pas utiliser l'objet de retour comme ça? : [


@ user1316208 Il doit créer un objet temporaire pour agir en tant que côté droit de l'opérateur = . P.s. Voir mon édition récente, j'espère que cela vous aide.


-Fno-Elide-constructeurs Le drapeau peut être utile si vous ne voyez pas des appels copieux-cors supplémentaires.


Merci @fredrickgauss, ce drapeau est ce que je cherche. Les compilateurs effectuent secrètement l'optimisation des élits pour nous, qui économise quelques destructions et des constructions de copie. Utilisation de -fno-Elide-constructeurs Pour désactiver ces optimisations et voir lorsque les destructions et les constructions se produisent.



5
votes

Comme Rob Dit, vous n'avez pas créé les trois opérateurs de constructeur / d'affectation que C ++ utilise. Ce que la règle de trois il a mentionné signifie que si vous déclarez un destructeur, un constructeur de copie ou un opérateur d'affectation ( opérateur = () ), vous devez utiliser les trois.

Si vous ne créez pas ces fonctions, le compilateur créera sa propre version d'eux pour vous. Cependant, les compilers Copy Copy Constructor et les opérateurs d'affectation ne font qu'une copie peu profonde d'éléments de l'objet d'origine. Cela signifie que l'objet copié créé comme valeur de retour, puis copié dans l'objet dans principal () a un pointeur à la même adresse que le premier objet que vous avez créé. Ainsi, lorsque cet objet d'origine est détruit pour faire de la place à l'objet copié, le tableau de classe de classe sur le tas est libéré, ce qui entraîne un pointeur de l'objet copié de devenir invalidée.


0 commentaires

2
votes

Si vous voulez voir quand une copie d'un objet est faite, faites ceci:

struct Foo {
    Foo() { std::cout << "default ctor\n"; }
    Foo(Foo const &) { std::cout << "copy ctor\n"; }
    Foo(Foo &&) { std::cout << "move ctor\n"; }
    Foo &operator=(Foo const &) { std::cout << "copy assign\n"; return *this; }
    Foo &operator=(Foo &&) { std::cout << "move assign\n"; return *this; }
    ~Foo() { std::cout << "dtor\n"; }
};

Foo Function(Foo* f){
   return *f;    
}

int main(int argc,const char *argv[])
{
   Foo* f=new Foo;

   for(int i=1;i<10;i++)
      *f = Function(f);

   delete f;
}


1 commentaires

Wow, grands esprits se ressemblent. Mais je n'avais pas l'opérateur de déménagement CTOR ou de déménagement.



8
votes

Obtenir (enfin) à ce que vous avez initialement destiné à poser à propos, la réponse courte est que c'est rarement un problème. La norme contient une clause qui exempte spécifiquement un compilateur de devoir utiliser le constructeur de copie sur une valeur de retour, même si le constructeur de copie a des effets secondaires, la différence est donc visible de manière externe.

Selon que vous retournez une variable, ou simplement une valeur, cela s'appelle l'optimisation de la valeur de retour nommée (NRVO) ou simplement une optimisation de la valeur de retour (RVO). Les compilateurs les plus raisonnablement modernes implémentent les deux (certains, tels que G ++, le font même lorsque vous désactivez l'optimisation).

Pour éviter de copier la valeur de retour, le compilateur passe l'adresse où la copie irait en tant que paramètre masqué à la fonction. La fonction construit ensuite sa valeur de retour à cet endroit, de sorte qu'une fois la fonction renvoie, la valeur est déjà là sans être copiée.

Ceci est assez courant et fonctionne assez bien que Dave Abrahams (à l'époque un membre du comité standard de la norme C ++) a écrit Un article Il y a quelques années montrant qu'avec des compilateurs modernes, les tentatives des gens à éviter l'extra La copie produise souvent du code qui est plus lent que si vous écrivez simplement un code simple et évident.


0 commentaires