• [^] # Re: Godbolt

    Posté par (Mastodon) . En réponse au journal Le quiz c++ de l'été. Évalué à 6.

    Non, ce n'est pas inversé. Je vais prendre un exemple avec std::string pour que ce soit plus clair. L'hypothèse de départ, c'est qu'on veut conserver la chaîne de caractères passé en paramètre (sinon, pas besoin de se poser la question, voir plus bas). Le cas le plus fréquent, c'est un constructeur, mais ça pourrait être n'importe quelle fonction.

    struct Toto {
     Toto(std::string s) : str(std::move(s)) { }
     std::string str;
    };
    struct Titi {
     Titi(const std::string& s) : str(s) { }
     std::string str;
    };
    int main() {
     std::string str1("str1");
     Toto toto1(str1); // (a)
     Toto toto2("str2"); // (b)
     Titi titi1(str1); // (c)
     Titi titi2("str2"); // (d)
    }

    En (a), il y a une copie qui est faite au moment de l'appel, mais dans le constructeur, il n'y a pas de copie, juste un déplacement (ce qui revient à déplacer le pointeur). En (b), il n'y a pas de copie, la chaîne est construite dans le paramètre qui est ensuite déplacé dans la variable membre. En (c), il y a un passage de référence au moment de l'appel, mais il y a une copie dans le constructeur. En (d), il y a une création d'une chaîne sur la pile, puis il y a une copie dans le constructeur. Au final, en passant par valeur, on s'évite une copie si on passe un littéral, alors qu'en passant par référence, on a une copie superflue. C'est pour ça que depuis C++11 (depuis qu'on a la sémantique de déplacement), il est conseillé de passer ce genre de paramètre par valeur, et plus par référence.

    Si on n'a pas besoin de conserver le paramètre, alors il faut passer le paramètre par référence constante. Mais ce cas était déjà géré avant C++11.

    Dans le cas d'un std::shared_ptr<T>, on peut tenir le même genre de raisonnement, mais il y a une subtilité en plus qui est qu'on a en fait deux objets : le pointeur intelligent en lui-même (std::shared_ptr), et l'objet pointé (de type T). Comme le précise Herb Sutter dans l'article que j'ai lié un peu plus haut, si on n'a besoin que de l'objet pointé, on doit passer soit une référence sur un T, soit un pointeur sur un T parce que ça rend la fonction plus générique en ce sens qu'on ne la restreint pas à une seule politique de gestion mémoire. Si on a besoin de partager le pointeur, alors on passe un shared_ptr<T> par copie (pour la même raison que pour le std::string précédemment). Si on passe un shared_ptr<T> par référence non-constante, c'est qu'on veut modifier le pointeur lui-même et pas l'objet pointé. Et si on le passe par référence constante, c'est qu'on ne sait pas si on va faire une copie ou pas, mais ce cas est extrêmement rare et c'est précisément celui qui est donné en exemple dans le post initial.