30 votes

Pourquoi utiliser une fonction plutôt qu'une référence à un membre?

J'étais en train de tester en examinant du code et j'ai remarqué quelque chose de similaire à :

template
class example{
    public:
        example(T t): m_value{t}{}

        const T &value = m_value;

    private:
        T m_value;
};

Je n'avais jamais vu ça avant. Presque toutes les API ou bibliothèques que j'ai utilisées auparavant définissent une fonction qui renvoie une variable membre comme ceci, et non une référence constante à celle-ci :

template
class example{
    public:
        example(T t): m_value{t}{}

        const T &value() const{
            return m_value;
        }

    private:
        T m_value;
};

Pourquoi la première méthode est-elle moins courante ? Quels en sont les inconvénients ?

34voto

gha.st Points 5271

Il existe plusieurs raisons pour lesquelles les fonctions (inline) qui renvoient une référence appropriée sont meilleures:

  1. Une référence nécessitera de la mémoire (généralement la même quantité qu'un pointeur) dans chaque objet

  2. Les références ont généralement le même alignement que les pointeurs, ce qui peut entraîner un alignement plus élevé de l'objet environnant et donc gaspiller encore plus de mémoire

  3. Initialiser une référence nécessite (une quantité minuscule de) temps

  4. Avoir un champ de membre de type référence désactivera les opérateurs de copie et de déplacement par défaut, car les références ne sont pas réaffectables

  5. Avoir un champ de membre de type référence provoquera la génération automatique incorrecte des constructeurs de copie et de déplacement par défaut, car ils contiendront maintenant des références aux membres d'autres objets

  6. Les fonctions peuvent effectuer des vérifications supplémentaires comme la vérification des invariants dans les versions de débogage

Sachez qu'en raison de l'inlining, la fonction ne générera généralement pas de coûts supplémentaires au-delà d'un binaire potentiellement légèrement plus grand.

3voto

adpalumbo Points 2587

Encapsulation.

Pour les puristes de la programmation orientée objet, m_value est un détail d'implémentation. Les consommateurs de la classe example devraient être en mesure d'utiliser une interface fiable pour accéder à value() et ne pas dépendre de la manière dont example détermine cela. Une version future de example (ou une spécialisation de modèle compliquée) pourrait vouloir utiliser une mise en cache ou un journal avant de retourner une value(); ou elle pourrait avoir besoin de calculer value() à la volée en raison de contraintes de mémoire.

Si vous n'utilisez pas une fonction d'accès au départ, tout ce qui utilise example pourrait devoir changer si vous changez d'avis plus tard. Et cela peut introduire toutes sortes de gaspillages et de bugs. Il est plus facile d'abstraire d'un niveau supplémentaire en fournissant un accesseur comme value().

D'un autre côté, certaines personnes ne sont tout simplement pas aussi rigides en matière de ces principes de POO et aiment simplement écrire du code efficient et lisible, gérant le refactoring quand cela se produit.

2voto

André Puel Points 3222

La première option nécessite une mémoire supplémentaire, tout comme un pointeur.

Si vous faites :

inline const T& value() const{
      return m_value;
}

vous avez le même avantage que la première approche sans avoir besoin de mémoire supplémentaire.

De plus, étant donné que la première approche nécessite C++11, il est moins probable que les gens l'utilisent.

1voto

Deduplicator Points 9096

Utilisation générale pour retourner des références constantes:
Retourner une référence constante est courant dans les cas où la création ou la destruction d'une copie est coûteuse.
En général, la règle est la suivante : Si le passage et l'utilisation d'une référence sont plus coûteux que la création d'une copie, ne le faites pas. Si on ne vous demande pas un sous-objet, c'est normalement même impossible.
Sinon, il s'agit d'une optimisation de performance valide de retourner des références constantes, qui est transparente pour le code source bien comporté.
Beaucoup de code de modèle retourne des références constantes même lorsque le test ci-dessus n'indique pas, juste pour traiter toutes les spécialisations de manière égale et parce que la fonction est vraiment petite et est presque garantie d'être inlinée de toute façon.

Pour en venir maintenant à l'essence de votre découverte curieuse (je n'ai jamais rien vu de tel jusqu'à présent) :
+ Pas besoin d'une fonction d'accesseur (néanmoins, c'est nul, cela sera de toute façon compilé)
- L'objet est plus gros (les références nécessitent normalement autant d'espace que les pointeurs)
- Besoin potentiel d'une meilleure alignement en raison du point ci-dessus.
- N'a pas de fonctions membres magiques, car le compilateur ne sait pas comment copier les références
- Pour les compilations de débogage, on ne peut pas ajouter de vérifications supplémentaires.

Le même aspect sans ces dommages collatéraux peut être réalisé de la manière suivante d'ailleurs (seules les vérifications supplémentaires pour le débogage restent impossibles) :

template
struct exemple {
    exemple(T t): value{t}{}
    union{
        const T value;
        struct{
        private:
            T value;
            friend class exemple;
        } _value;
    };
};

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