web-dev-qa-db-fra.com

Comment "retourner un objet" en C ++?

Je sais que le titre me 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).

Dans 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 options

(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
}

Alors utilisez-le comme ça

Thing thing;
calculateThing(thing);

(2) Ou je peux retourner un pointeur sur un objet alloué dynamiquement

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

Alors utilisez-le comme ça

Thing* thing = calculateThing();
delete thing;

En utilisant la première approche, je n'aurai pas besoin de 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, je dois me souvenir de delete thing;, qui n'a pas l'air très gentil. Je ne veux pas renvoyer une valeur copiée parce que c'est inefficace (je pense), alors voici les questions

  • Existe-t-il une troisième solution (ne nécessitant pas de copier la valeur)?
  • Y at-il un problème si je m'en tiens à la première solution?
  • Quand et pourquoi devrais-je utiliser la deuxième solution?
152
phunehehe

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

Prouve le.

Recherchez RVO et NRVO et, dans C++ 0x, la sémantique du déplacement. Dans la plupart des cas, en C++ 03, un paramètre out est simplement un bon moyen de rendre votre code laid, et en C++ 0x, vous vous blesseriez en utilisant un paramètre out.

Il suffit d'écrire du code en clair, retour par valeur. Si les performances sont un problème, profilez-le (arrêtez de deviner) et trouvez ce que vous pouvez faire pour le résoudre. Cela ne va probablement pas renvoyer des choses à partir de fonctions.


Cela dit, si vous êtes prêt à écrire de la sorte, vous voudrez probablement définir le paramètre out. Cela évite l'allocation dynamique de mémoire, qui est plus sûre 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 souhaitez utiliser l'allocation dynamique, le moins que vous puissiez faire est de la placer dans un pointeur intelligent. (Cela devrait être fait tout le temps de toute façon) Alors vous ne vous inquiétez pas de supprimer quoi que ce soit, les choses sont exceptionnellement sûres, etc. Le seul problème est que c'est probablement plus lent que de revenir en valeur de toute façon!

103
GManNickG

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 écrivez simplement un code lisible (vous devrez exécuter un profileur plus tard - mais ne pré-optimisez pas).

41
Amir Rachum

Il suffit de retourner un objet comme celui-ci:

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

Ceci appellera le constructeur de copie sur Things, vous voudrez peut-être donc faire votre propre implémentation de cela. Comme ça:

Thing(const Thing& aThing) {}

Cela pourrait être un peu plus lent, mais ce n’est peut-être pas un problème.

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 le fait remarquer dreamlax dans le commentaire).

Avez-vous essayé d'utiliser des pointeurs intelligents (si Thing est un objet très gros et lourd), comme auto_ptr:


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


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

  // auto_ptr frees thing 
}
11
demetrios

Un moyen rapide de déterminer si un constructeur de copie est appelé consiste à 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 "Le constructeur de copie a été appelé" variera entre 0, 1 et 2. Si vous n'en obtenez aucune, votre compilateur a optimisé la valeur de retour (ce qu'il est autorisé à faire). Si vous obtenez n'obtenez pas 0 et que votre constructeur de copie est ridiculement coûteux, , puis recherchez d'autres moyens de renvoyer des instances à partir de vos fonctions.

8
dreamlax

Premièrement, vous avez une erreur dans le code, vous voulez dire que Thing *thing(new Thing());, et seulement return thing;.

  • Utilisation shared_ptr<Thing>. Deref comme si c'était un pointeur. Il sera supprimé pour vous lorsque la dernière référence au Thing contenu sortira de sa portée.
  • La première solution est très courante dans les bibliothèques naïves. Il a des performances et une surcharge syntaxique, évitez-le si possible
  • Utilisez la deuxième solution uniquement si vous pouvez être sûr qu'aucune exception ne sera levée, ou si les performances sont absolument essentielles (vous vous connecterez avec C ou Assembly avant que cela ne devienne pertinent).
1
Matt Joiner

Je suis sûr qu'un expert en C++ apportera une meilleure réponse, mais personnellement, j'aime bien la deuxième approche. L'utilisation de pointeurs intelligents aide à résoudre le problème d'oubli de delete et, comme vous le dites, cela semble plus simple que de devoir créer un objet à l'avance (et de toujours le supprimer si vous souhaitez l'allouer sur le tas).

0
EMP