Pourquoi le constructeur de copie n’est-il pas appelé dans ce code

Alors, pourquoi le constructeur de copie n’est-il pas appelé dans la fonction ” const Integer operator + (const Integer & rv) “. Est-ce à cause de RVO. Si oui, que dois-je faire pour l’empêcher?

#include  using namespace std; class Integer { int i; public: Integer(int ii = 0) : i(ii) { cout << "Integer()" << endl; } Integer(const Integer &I) { cout << "Integer(const Integer &)" << endl; } ~Integer() { cout << "~Integer()" << endl; } const Integer operator+(const Integer &rv) const { cout << "operator+" << endl; Integer I(i + rv.i); I.print(); return I; } Integer &operator+=(const Integer &rv) { cout << "operator+=" << endl; i + rv.i; return *this; } void print() { cout << "i: " << i << endl; } }; int main() { cout << "built-in tpes:" << endl; int i = 1, j = 2, k = 3; k += i + j; cout << "user-defined types:" << endl; Integer ii(1), jj(2), kk(3); kk += ii + jj; } 

Je reçois une erreur Si je commente le constructeur de copie. Je m’attends à ce que le constructeur de copie soit appelé lorsque l’opérateur + revient. Voici le résultat du programme

 built-in tpes: user-defined types: Integer() Integer() Integer() operator+ Integer() i: 3 // EXPECTING Copy Constructor to be called after this operator+= ~Integer() ~Integer() ~Integer() ~Integer() 

Est-ce à cause de RVO. Si oui, que dois-je faire pour l’empêcher?

Oui. Mais il n’a pas été appelé à cause de l’ optimisation de la valeur de retour par le compilateur.

Si vous utilisez GCC, utilisez l’ -fno-elide-constructors pour l’éviter.

Le manuel de GCC 4.6.1 dit,

-fno-elide-constructors

La norme C ++ permet à une implémentation d’omettre la création d’un temporaire qui n’est utilisé que pour initialiser un autre object du même type. Spécifier cette option désactive cette optimisation et oblige G ++ à appeler le constructeur de copie dans tous les cas.

(N) RVO est l’un des optimisations les plus faciles à mettre en œuvre. Dans la plupart des conventions d’appel pour le retour valeur, l’appelant réserve l’espace pour l’object renvoyé, puis transmet un pointeur masqué à la fonction. La fonction construit ensuite l’object dans l’adresse fournie. C’est-à-dire, kk += ii + jj; est traduit en quelque chose comme:

 Integer __tmp; // __rtn this arg Integer::operator+( &tmp, &ii, jj ); kk += __tmp; 

La fonction (dans ce cas, Integer::operator+ prend un premier argument caché __rtn qui est un pointeur sur un bloc non initialisé de mémoire de sizeof(Integer) octets, où l’object doit être construit, un second argument caché this , puis argument à la fonction dans le code.

Ensuite, l’implémentation de la fonction est traduite en:

 Integer::operator+( Integer* __rtn, Integer const * this, const Integer &rv) { cout << "operator+" << endl; new (__rtn) Integer(i + rv.i); __rtn->print(); } 

Comme la convention d’appel transmet le pointeur, il n’est pas nécessaire que la fonction réserve de l’espace supplémentaire pour un entier local qui serait ensuite copié, car elle peut simplement créer le I de votre code directement dans le pointeur reçu et éviter la copie.

Notez que le compilateur ne peut pas toujours exécuter NRVO, en particulier si vous avez deux objects locaux dans la fonction et que vous renvoyez l’un ou l’autre en fonction d’une condition non déductible du code (par exemple, la valeur d’un argument de la fonction). ). Bien que vous puissiez faire cela pour éviter RVO, le fait est que cela rendra votre code plus complexe, moins efficace et plus difficile à maintenir.