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
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!
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).
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
}
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.
Premièrement, vous avez une erreur dans le code, vous voulez dire que Thing *thing(new Thing());
, et seulement return thing;
.
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.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).