Projet

Général

Profil

Getters par référence et non par valeur

Je vais essayer de vous démontrer le bien fondé du passage par référence plutôt que par valeur.
En effet, le passage ou le retour d'une variable par valeur, fait que votre programme va créer une instance temporaire de celle-ci pour la retourner. Cette création temporaire a un coût. Dans le cas des types dits "simple" (int, string, bool...), cela n'est pas trop gênant.

Par contre, si une méthode retourne une liste (très très longue), ou un objet, les renvoyer par valeur appellera donc ce mécanisme de copie et là, votre programme va mouliner (tout dépend de la machine bien-sûr). Même si la puissance des ordinateurs est telle qu'une copie d'un tableau de 100 éléments dans la mémoire ne se voit plus (tout dépend des éléments, disons des entiers), elle ne doit pas nous pousser à faire n'importe quoi dans nos implémentations :).

Aussi, mieux que des explications, voici l'exemple =).
Pour faciliter la compréhension, tout sera dans un unique fichier "main.cpp", avec la déclaration de la classe, et l'utilisation de celle-ci dans le main.

Prenons une simple classe Personne, dotée d'un constructeur prenant trois paramètres (nom, prénom, âge).
Ces trois paramètres seront passés par référence constante afin que là aussi, le paramètre passé ne soit pas une copie mais le même objet (c'est dur à suivre, mais tout va s'éclairer :p).
Ensuite, pour vous prouver le bénéfice des références, nous aurons deux getters : un retournant une valeur et un retournant une référence.

Dans le main, nous afficherons les adresses des différents objets que nous retournerons par ces deux getters, ce sera notre preuve =).
Place au code :

#include <QDebug>

class Personne{
public:

    /** Constructeur */
    Personne(const QString& nom, const QString& prenom, int age) {_nom = nom; _prenom = prenom; _age = age;}
    /** Getter retournant une valeur */
    QString getNom() { return _nom;}
    /** Getter retournant une référence */
    const QString& getNomRefConstante() {return _nom;}

private:
    QString _nom;
    QString _prenom;
    int     _age;
};

int main()
{
    /** Création d'un objet de type Personne */
    Personne p1("toto", "l'asticot", 12);

    /** Affichage de l'adresse des objets retournés par les différentes méthodes */
    qDebug() << "--- Appels consecutifs de getNomRefConstante() ---";
    qDebug() << &p1.getNomRefConstante();
    qDebug() << &p1.getNomRefConstante();

    qDebug() << "--- Appels consecutifs de getNom() ---";
    qDebug() << &p1.getNom();
    qDebug() << &p1.getNom();

    return 0;
}

On appelle les méthodes deux fois successivement afin de voir si l'adresse de l'objet retourné est la même ou non.
Si c'est la même adresse, l'objet n'est pas une copie mais l'objet lui-même. Si ce n'est pas la même adresse, vous aurez compris que le programme a créé une copie de notre objet.

D'ailleurs pas besoin de l'exécuter pour s'en rendre compte. En compilant, on doit voir passer deux avertissements du compilateur (qu'est-ce qu'il est intelligent).
En effet, celui-ci vous dit "avertissement : taking address of temporary" à l'endroit ou on essaye d'afficher les adresses des objets retournés par la méthode getNom() (retour par valeur).
Cela veut donc dire que le programme va afficher des adresses d'objets temporaires. La preuve est faite =).
Mais, pour le fun, voyons voir ce que nous dit le programme :

--- Appels consecutifs de getNomRefConstante() --- 
0x7fffcdfd0e90 
0x7fffcdfd0e90 
--- Appels consecutifs de getNom() --- 
0x7fffcdfd0f50 
0x7fffcdfd0f30

Attention, votre affichage peut différer, c'est l'adresse mémoire que mon système a alloué au programme à un instant T.
Si on s'en tient à ce qui est affiché, les deux appels à la méthode nous retournant une référence ont la même adresse, et donc le même objet.
Et ça c'est bien :).

En revanche, les deux appels à la méthode nous retournant une valeur affichent deux adresses différentes.
Cela veut dire que pour avoir la même information (à savoir la valeur de l'attribut _nom de notre objet personne), la méthode getNom() a fabriqué deux objets temporaires (car deux appels à la méthode).

Cependant, cette page n'est pas là pour vous dire que le passage par valeur ne sert à rien.
Il est valable dans bien des cas, comme par exemple vouloir effectuer des traitements sur un objet dans une méthode, et que l'on veuille retrouver cette objet comme à son origine au sortir de cette méthode.
Là, le passage par valeur est préconisé afin de travailler sur une copie de l'objet désiré.

Redmine Appliance - Powered by TurnKey Linux