6 votes

Passer par valeur ou référence, à un constructeur C++ qui doit stocker une copie ?

Un constructeur de valeurs C++ (implicite ou explicite) doit-il accepter son ou ses paramètres par valeur ou par référence à une constante, lorsqu'il doit stocker une copie de l'argument ou des arguments dans son objet, de l'une ou l'autre manière ?

Voici l'exemple le plus court auquel je peux penser :

struct foo {
    bar _b;
    foo(bar [const&] b) // pass by value or reference-to-const?
        : _b(b) { }
};

L'idée ici est que je veux minimiser les appels au constructeur de copie de bar quand un objet foo est créé, de n'importe laquelle des différentes manières dont un objet foo pourrait être créé.

Veuillez noter que je m'y connais un peu en élision de copie et en optimisation des valeurs de retour (nommées), et que j'ai lu les documents suivants "Vous voulez de la vitesse ? Passez par la valeur" Cependant, je ne pense pas que l'article aborde directement ce cas d'utilisation.

Editar: Je devrais être plus précis.

Supposons que je ne puisse pas connaître le sizeof(bar) ou si oui ou non bar est un type fondamental et intégré ( bar peut être un paramètre de modèle, et foo peut être un modèle de classe au lieu d'une classe). De même, ne supposez pas que foo Le constructeur de l'utilisateur peut être inlined (ou bar d'ailleurs). Je suppose que j'utilise au moins un compilateur qui implémente RVO.

Ce que j'aimerais, c'est qu'il y ait une possibilité (compte tenu des optimisations du compilateur) qu'un appel comme celui-ci n'invoque aucun appel à bar (même lors de l'exécution de la fonction de copie). _b(b) sur foo La liste d'initialisation de l'entreprise) :

foo f = function_that_creates_and_returns_a_bar_object_using_rvo();

Est-il possible (compte tenu de la norme C++98) que cela soit fait, et si oui, est-il plus ou moins probable que cela fonctionne si foo accepte son paramètre par référence-à-const plutôt que par valeur ?

5voto

Doug T. Points 33360

Toutes choses égales par ailleurs, je passe par const& pour les classes suffisamment complexes, et value pour POD et les objets simples.

Présentation des avantages et inconvénients de la transmission par référence constante au lieu de la transmission traditionnelle par valeur.

Les points positifs :

  • Evite une copie (gros avantage pour les objets dont la copie est coûteuse)
  • Accès en lecture seule

Négatifs :

  • Quelqu'un peut lancer la constante de la référence s'il le veut vraiment.

Le plus important dans les points positifs est que vous explicitement contrôler le moment où la copie se produit (dans votre cas en passant l'initialisation de _b dans votre liste d'initialisateurs). En pensant aux points négatifs... Je suis d'accord pour dire que c'est un risque. Je pense que presque tous les bons programmeurs se sentiront mal à l'aise d'empiler le const_cast. De plus, vous pouvez consciencieusement rechercher const_cast et jeter des tomates à la personne qui a coulé le const de l'argument. Mais bon, on ne sait jamais et qui a le temps de regarder le code comme un faucon :) ?

Mon opinion subjective est que dans des classes suffisamment complexes et dans des environnements où les performances sont importantes, l'avantage d'éviter le constructeur de copie l'emporte sur le risque. Cependant, pour les classes vraiment stupides et POD, j'ai tendance à faire des copies des données et à passer par valeur.

4voto

Alexey Malistov Points 13526

Regarde ça. pregunta .

Utilice const T & arg si sizeof(T)>sizeof(void*) et utiliser T arg si sizeof(T) <= sizeof(void*) . Tous les types fondamentaux devraient être l'exception à cette règle.

2voto

Kristopher Johnson Points 34554

D'un point de vue stylistique, je dirais que le passage par référence est la meilleure solution.

Si la performance est vraiment importante, alors ne devinez pas. Mesurez-la.

2voto

SuperWild1 Points 236

Je n'ai pas vérifié ce que dit la norme, mais en essayant ceci empiriquement, je peux vous dire que GCC n'optimise pas la copie, que le constructeur accepte l'argument par valeur ou par référence constante.

Si, toutefois, le constructeur prend une référence const, il parvient à éviter une copie inutile lorsque vous créez foo à partir d'un bar objet.

Pour résumer :

Bar b = makeBar();         // No copy, because of RVO
FooByValue f1 = makeBar(); // Copy constructor of Bar called once
FooByRef f2 = makeBar();   // Copy constructor of Bar called once
FooByValue f3(b);          // Copy constructor of Bar called twice
FooByRef f4(b);            // Copy constructor of Bar called only once

Je ne suis pas un expert en compilation, mais je suppose qu'il est logique qu'il ne puisse généralement pas renvoyer une valeur de retour dans un endroit arbitraire (comme le champ membre de l'élément de commande). foo objet). Au lieu de cela, il faut que la cible soit en haut de la pile.

2voto

Fred Points 146

En C++98 et C++03, vous devez passer la commande const& bar et ensuite copier. En C++0x, vous devez passer bar et ensuite faire un déplacement (à condition que bar a un constructeur de déplacement).

#include <utility>

struct foo
{
    bar _b;

    foo(bar b) : _b(std::move(b)) {}
};

Si vous construisez foo avec un paramètre de type lvalue, le constructeur de copie sera appelé pour créer une copie. b et cette copie sera déplacée dans _b . Si vous construisez foo avec un paramètre rvalue, bar Le constructeur de move sera appelé pour se déplacer vers b et ensuite, il sera déplacé à nouveau dans _b .

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