189 votes

Comment "retourner un objet" 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 pile et les mettre sur le tas).

En Java, je peux toujours renvoyer des références à des objets "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 possibilités

(1) Je peux utiliser des références chaque fois que j'ai besoin de "retourner" un objet.

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

Ensuite, utilisez-le comme ceci

Thing thing;
calculateThing(thing);

(2) Ou je peux renvoyer un pointeur vers un objet alloué dynamiquement.

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

Ensuite, utilisez-le comme ceci

Thing* thing = calculateThing();
delete thing;

En utilisant la première approche, je n'aurai pas à libérer 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; ce qui n'est pas très joli. Je ne veux pas retourner une valeur copiée parce que c'est inefficace (je pense), alors voici les questions que je me pose

  • 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 dois-je utiliser la deuxième solution ?

42 votes

+1 pour avoir joliment posé la question.

1 votes

Pour être très pédant, il est un peu imprécis de dire que "les fonctions renvoient quelque chose". Plus correctement, l'évaluation d'un appel de fonction produit une valeur . La valeur est toujours un objet (sauf si c'est une fonction void). La distinction est de savoir si la valeur est une glvalue ou une prvalue -- ce qui est déterminé par le fait que l'on déclare ou non type de retour est une référence ou non.

111voto

GManNickG Points 155079

Je ne veux pas retourner une valeur copiée parce que c'est inefficace.

Prouvez-le.

Recherchez RVO et NRVO, et dans C++0x la sémantique du mouvement. Dans la plupart des cas en C++03, un paramètre out n'est qu'un bon moyen d'enlaidir votre code, et en C++0x vous vous feriez du mal en utilisant un paramètre out.

Il suffit d'écrire du code propre, de retourner par valeur. Si la performance est un problème, établissez un profil (arrêtez de deviner), et trouvez ce que vous pouvez faire pour le résoudre. Il ne s'agira probablement pas de retourner des choses à partir de fonctions.


Cela dit, si vous tenez absolument à écrire comme ça, vous devriez probablement faire le paramètre de sortie. Il évite l'allocation dynamique de la mémoire, ce qui est plus sûr et généralement plus rapide. Cela nécessite que vous ayez un moyen de construire l'objet avant d'appeler la fonction, ce qui n'a pas toujours de sens pour tous les objets.

Si vous voulez utiliser l'allocation dynamique, le moins que l'on puisse faire est de la mettre dans un pointeur intelligent. (Cela devrait être fait tout le temps de toute façon). Ainsi, vous n'avez pas à vous soucier de la suppression de quoi que ce soit, les choses sont protégées contre les exceptions, etc. Le seul problème est que c'est probablement plus lent que de retourner par valeur de toute façon !

0 votes

Merci GMan, je suis en train d'étudier le C++ et j'ai lu quelque part que lorsque l'on retourne une valeur, celle-ci doit être copiée ailleurs et la copie semble inefficace, n'est-ce pas ?

13 votes

@phunehehe : Il ne sert à rien de spéculer, vous devriez profiler votre code et le découvrir. (Indice : non.) Les compilateurs sont très intelligents, ils ne vont pas perdre de temps à copier des choses autour d'eux s'ils n'ont pas à le faire. Même si Si la copie coûte quelque chose, vous devez toujours vous efforcer d'avoir un bon code plutôt qu'un code rapide ; un bon code est facile à optimiser lorsque la vitesse devient un problème. Il ne sert à rien d'enlaidir le code pour quelque chose dont on ne sait pas si c'est un problème, surtout si on le ralentit ou si on n'en tire rien. Et si vous utilisez C++0x, la sémantique de déplacement rend ce problème inexistant.

0 votes

@phunehehehe cela peut aussi être utile dans certains cas, herbsutter.com/2008/01/01/…

48voto

Amir Rachum Points 13236

Il suffit de créer l'objet et de le retourner

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

Je pense que vous vous rendrez service si vous oubliez l'optimisation et que vous vous contentez d'écrire du code lisible (vous aurez besoin d'utiliser un profileur plus tard - mais ne pré-optimisez pas).

0 votes

Comment cela fonctionne-t-il en C++98 ? J'obtiens des erreurs sur l'interpréteur CINT et je me demandais si c'était dû à C++98 ou à CINT lui-même... !

22voto

Il suffit de retourner un objet comme ceci :

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

Cela invoquera le constructeur de copie sur les choses, donc vous pourriez vouloir faire votre propre implémentation de cela. Comme ceci :

Thing(const Thing& aThing) {}

Cela peut être un peu plus lent, mais ce n'est peut-être pas un problème du tout.

Mise à jour

Le compilateur optimisera probablement l'appel au constructeur de la copie, de sorte qu'il n'y aura pas de surcharge supplémentaire. (Comme dreamlax l'a souligné dans le commentaire).

9 votes

Thing thing(); déclare une fonction locale retournant un Thing En outre, la norme permet au compilateur d'omettre le constructeur de copie dans le cas que vous avez présenté ; tout compilateur moderne le fera probablement.

1 votes

Vous apportez un bon point en implémentant le constructeur de copie, surtout si une copie profonde est nécessaire.

0 votes

+1 pour la mention explicite du constructeur de copie, bien que comme @dreamlax le dit, le compilateur va très probablement "optimiser" le code de retour pour les fonctions en évitant un appel pas vraiment nécessaire au constructeur de copie.

13voto

demetrios Points 119

Avez-vous essayé d'utiliser des pointeurs intelligents (si la Chose est un objet vraiment gros et lourd), comme shared_ptr :

    std::shared_ptr calculateThing()
    {
        std::shared_ptr<Thing> thing(new Thing);
        // .. some calculations
        return thing;
    }

    // ...
    {
        std::shared_ptr<Thing> thing = calculateThing();
        // working with thing

        // shared_ptr frees thing 
    }

4 votes

auto_ptr sont obsolètes ; utilisez shared_ptr ou unique_ptr à la place.

0 votes

Je vais juste ajouter ceci ici... J'utilise c++ depuis des années, mais pas professionnellement... J'ai décidé d'essayer de ne plus utiliser les pointeurs intelligents, ils sont juste un désordre absolu imo et causent toutes sortes de problèmes, n'aidant pas vraiment à accélérer le code non plus. Je préfère de loin copier les données et gérer les pointeurs moi-même, en utilisant RAII. Je vous conseille donc, si vous le pouvez, d'éviter les pointeurs intelligents.

9voto

dreamlax Points 47152

Un moyen rapide de déterminer si un constructeur de copie est appelé est d'ajouter 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 "Copy constructor was called" que vous obtiendrez variera entre 0, 1 et 2. Si vous n'en obtenez aucune, alors votre compilateur a optimisé la valeur de retour (ce qu'il est autorisé à faire). Si vous n'obtenez pas 0, alors votre constructeur de copie est ridiculement cher, entonces rechercher des moyens alternatifs pour renvoyer les instances de vos fonctions.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X