Comment «retourner un object» en C ++?

Je sais que le titre semble familier car il y a beaucoup de questions similaires, mais je demande un aspect différent du problème (je connais la différence entre avoir des choses sur la stack et les mettre sur le tas).

En Java, je peux toujours renvoyer des références à des objects “locaux”

public Thing calculateThing() { Thing thing = new Thing(); // do calculations and modify thing return thing; } 

En C ++, pour faire quelque chose de similaire, j’ai 2 options

(1) Je peux utiliser des références chaque fois que je dois “retourner” un object

 void calculateThing(Thing& thing) { // do calculations and modify thing } 

Alors utilise-le comme ça

 Thing thing; calculateThing(thing); 

(2) Ou je peux renvoyer un pointeur sur un object alloué dynamicment

 Thing* calculateThing() { Thing* thing(new Thing()); // do calculations and modify thing return thing; } 

Alors utilise-le comme ça

 Thing* thing = calculateThing(); delete thing; 

En utilisant la première approche, je n’aurai pas à libérer de la mémoire manuellement, mais pour moi, cela rend le code difficile à lire. Le problème avec la deuxième approche est que je dois me rappeler de delete thing; , qui n’a pas l’air très bien. Je ne veux pas retourner une valeur copiée car elle est inefficace (je pense), alors voici les questions

  • Existe-t-il une troisième solution (qui ne nécessite pas de copier la valeur)?
  • Y a-t-il un problème si je m’en tiens à la première solution?
  • Quand et pourquoi devrais-je utiliser la seconde solution?

    Je ne veux pas retourner une valeur copiée car elle est inefficace

    Prouve le.

    Recherchez RVO et NRVO, et dans C ++ 0x move-sémantique. Dans la plupart des cas, en C ++ 03, un paramètre out est un bon moyen de rendre votre code inintéressant, et en C ++ 0x, vous vous feriez mal en utilisant un paramètre out.

    Écrivez simplement le code propre, retournez par valeur. Si les performances posent problème, définissez-les (arrêtez de deviner) et trouvez ce que vous pouvez faire pour y remédier. Il ne sera probablement pas de retour des choses des fonctions.


    Cela dit, si vous êtes prêt à écrire comme ça, vous voudrez probablement faire le paramètre out. Il évite l’allocation dynamic de la mémoire, plus sûre et généralement plus rapide. Cela nécessite que vous ayez un moyen de construire l’object avant d’appeler la fonction, ce qui n’a pas toujours un sens pour tous les objects.

    Si vous souhaitez utiliser l’allocation dynamic, le moins que l’on puisse faire est de le placer dans un pointeur intelligent. (Cela devrait être fait tout le temps de toute façon) Alors vous ne vous souciez pas de supprimer quoi que ce soit, les choses sont sûres, etc.

    Il suffit de créer l’object et de le retourner

     Thing calculateThing() { Thing thing; // do calculations and modify thing return thing; } 

    Je pense que vous allez vous rendre service si vous oubliez l’optimisation et que vous écrivez simplement du code lisible (vous devrez exécuter un profileur plus tard – mais ne pré-optimisez pas).

    Renvoyez simplement un object comme ceci:

     Thing calculateThing() { Thing thing(); // do calculations and modify thing return thing; } 

    Cela invoquera le constructeur de copie sur les choses, vous souhaiterez peut-être faire votre propre implémentation. Comme ça:

     Thing(const Thing& aThing) {} 

    Cela pourrait ralentir un peu, mais cela pourrait ne pas être un problème du tout.

    Mettre à jour

    Le compilateur optimisera probablement l’appel au constructeur de copie, donc il n’y aura pas de surcharge supplémentaire. (Comme Dreamlax souligné dans le commentaire).

    Avez-vous essayé d’utiliser des pointeurs intelligents (si Thing est vraiment un object volumineux et lourd), comme auto_ptr:

     std::auto_ptr calculateThing() { std::auto_ptr thing(new Thing); // .. some calculations return thing; } // ... { std::auto_ptr thing = calculateThing(); // working with thing // auto_ptr frees thing } 

    Un moyen rapide de déterminer si un constructeur de copie est appelé consiste à append une journalisation au constructeur de copie de votre classe:

     MyClass::MyClass(const MyClass &other) { std::cout < < "Copy constructor was called" << std::endl; } MyClass someFunction() { MyClass dummy; return dummy; } 

    Appelez someFunction ; le nombre de lignes appelées "constructeur de copie appelé" variera entre 0, 1 et 2. Si vous n'en obtenez pas, votre compilateur a optimisé la valeur de retour (ce qu'il est autorisé à faire). Si vous n'obtenez pas 0, et que votre constructeur de copie est ridiculement cher, recherchez ensuite d'autres moyens de renvoyer des instances de vos fonctions.

    Tout d’abord, vous avez une erreur dans le code, vous voulez avoir une Thing *thing(new Thing()); et ne return thing; que return thing; .

    • Utilisez shared_ptr . Deref c’est comme si c’était un pointeur. Il sera supprimé pour vous lorsque la dernière référence à la Thing contenue sera hors de scope.
    • La première solution est très courante dans les bibliothèques naïves. Il a des performances et des frais de syntaxe, évitez-le si possible
    • N’utilisez la deuxième solution que si vous pouvez garantir qu’aucune exception ne sera levée ou lorsque les performances sont absolument critiques (vous allez interagir avec C ou l’assemblage avant que cela ne devienne pertinent).

    Je suis sûr qu’un expert C ++ apportera une meilleure réponse, mais personnellement, j’aime la deuxième approche. L’utilisation de pointeurs intelligents facilite le problème de la delete et, comme vous le dites, il semble plus propre que de devoir créer un object avant le main (et de le supprimer si vous voulez l’allouer sur le tas).