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 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 renvoyer un pointeur vers 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 à 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 m'en souvenir delete thing;
, qui n'a pas l'air très bien. Je ne veux pas renvoyer 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 deuxième solution?
la source
Réponses:
Prouve le.
Recherchez RVO et NRVO, et dans la sémantique de déplacement C ++ 0x. Dans la plupart des cas en C ++ 03, un paramètre out est juste un bon moyen de rendre votre code moche, et en C ++ 0x, vous vous feriez mal en utilisant un paramètre out.
Écrivez simplement du code propre, retournez par valeur. Si les performances sont un problème, profilez-le (arrêtez de deviner) et trouvez ce que vous pouvez faire pour y remédier. Cela ne retournera probablement pas les choses des fonctions.
Cela dit, si vous êtes déterminé à écrire comme ça, vous voudrez probablement utiliser le paramètre out. Il é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 l'on puisse faire est de la placer dans un pointeur intelligent. (Cela devrait être fait tout le temps de toute façon) Ensuite, vous ne vous inquiétez pas de supprimer 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!
la source
Créez simplement l'objet et renvoyez-le
Je pense que vous vous ferez une faveur si vous oubliez l'optimisation et écrivez simplement du code lisible (vous devrez exécuter un profileur plus tard - mais ne pré-optimisez pas).
la source
Thing thing();
déclare une fonction locale et renvoie un fichierThing
.Renvoyez simplement un objet comme celui-ci:
Cela appellera le constructeur de copie sur Things, donc vous voudrez peut-être en faire votre propre implémentation. Comme ça:
Cela peut fonctionner un peu plus lentement, mais ce n'est peut-être pas du tout un problème.
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).
la source
Thing thing();
déclare une fonction locale renvoyant unThing
, également, le standard permet au compilateur d'omettre le constructeur de copie dans le cas que vous avez présenté; n'importe quel compilateur moderne le fera probablement.Avez-vous essayé d'utiliser des pointeurs intelligents (si Thing est un objet vraiment gros et lourd), comme auto_ptr:
la source
auto_ptr
s sont obsolètes; utilisezshared_ptr
ou à launique_ptr
place.Un moyen rapide de déterminer si un constructeur de copie est appelé est d'ajouter la journalisation au constructeur de copie de votre classe:
Appel
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 ne prenez pas 0, et votre constructeur de copie est ridiculement cher, puis chercher d' autres moyens pour retourner les instances de vos fonctions.la source
Premièrement, vous avez une erreur dans le code, vous voulez avoir
Thing *thing(new Thing());
, et seulementreturn thing;
.shared_ptr<Thing>
. Deref comme si c'était un pointeur. Il sera supprimé pour vous lorsque la dernière référence auThing
contenu sortira du champ d'application.la source
Je suis sûr qu'un expert C ++ viendra avec une meilleure réponse, mais personnellement j'aime la deuxième approche. L'utilisation de pointeurs intelligents aide à résoudre le problème de l'oubli
delete
et, comme vous le dites, cela semble plus propre que de devoir créer un objet au préalable (et de devoir toujours le supprimer si vous souhaitez l'allouer sur le tas).la source