Question

C'est indiqué dans [C++11 :12.8/31] :

Cette élision des opérations de copie/déplacement, appelée élision de copie, est autorisée [...] :

— dans une instruction return dans une fonction avec un type de retour de classe, lorsque l'expression est le nom d'un objet automatique non volatile (autre qu'un paramètre de fonction ou de clause catch) avec le même type cv non qualifié que le type de retour de la fonction, l'opération de copie/déplacement peut être omise en construisant l'objet automatique directement dans la valeur de retour de la fonction

Cela implique

#include <iostream>

using namespace std;

struct X
{
    X() { }
    X(const X& other) { cout << "X(const X& other)" << endl; }
};

X no_rvo(X x) {
    cout << "no_rvo" << endl;
    return x;
}

int main() {
    X x_orig;
    X x_copy = no_rvo(x_orig);

    return 0;
}

imprimera

X(const X& other)
no_rvo
X(const X& other)

Pourquoi le deuxième constructeur de copie est-il requis ?Un compilateur ne peut-il pas simplement prolonger la durée de vie de X?

Était-ce utile?

La solution

Imaginer no_rvo est défini dans un fichier différent de celui main de sorte que lors de la compilation main le compilateur ne verra que la déclaration

X no_rvo(X x);

et n'aura aucune idée si l'objet de type X est revenu a n'importe lequel rapport à l'argumentation.D'après ce qu'il sait à ce moment-là, la mise en œuvre de no_rvo pourrait tout aussi bien être

X no_rvo(X x) { X other; return other; }

Alors, quand par ex.compile la ligne

X const& x = no_rvo(X());

il fera ce qui suit, lors de l'optimisation maximale.

  • Générer le X temporaire à transmettre no_rvo comme argument
  • appel no_rvo, et liez sa valeur de retour à x
  • détruire l'objet temporaire auquel il est passé no_rvo.

Maintenant, si la valeur de retour de no_rvo serait le même objet que l'objet qui lui a été transmis, alors la destruction de l'objet temporaire signifierait la destruction de l'objet renvoyé.Mais ce serait une erreur car l'objet renvoyé est lié à une référence, prolongeant ainsi sa durée de vie au-delà de cette instruction.Cependant, ne pas détruire l’argument n’est pas non plus une solution, car ce serait une erreur si la définition de no_rvo est l'implémentation alternative que j'ai montrée ci-dessus.Ainsi, si la fonction est autorisée à réutiliser un argument comme valeur de retour, des situations peuvent survenir dans lesquelles le compilateur ne peut pas déterminer le comportement correct.

Notez qu'avec des implémentations courantes, le compilateur ne serait de toute façon pas en mesure d'optimiser cela, ce n'est donc pas une si grosse perte que cela ne soit pas formellement autorisé.Notez également que le compilateur est autorisé à optimiser la copie de toute façon s'il peut prouver que cela n'entraîne pas de changement dans le comportement observable (la soi-disant règle comme si).

Autres conseils

L'implémentation habituelle de RVO est que le code appelant transmet l'adresse d'un morceau de mémoire où la fonction doit construire son objet résultat.

Lorsque le résultat de la fonction est directement une variable automatique qui n'est pas un argument formel, cette variable locale peut simplement être placée dans le bloc de mémoire fourni par l'appelant, et l'instruction return n'effectue alors aucune copie.

Pour un argument passé par valeur, le code de la machine appelante doit copier-initialiser son argument réel à l'emplacement de l'argument formel avant de passer à la fonction.Pour que la fonction y place son résultat, elle devrait d'abord détruire l'objet argument formel, ce qui présente des cas particuliers délicats (par exemple, lorsque cette construction fait directement ou indirectement référence à l'objet argument formel).Ainsi, au lieu d'identifier l'emplacement du résultat avec l'emplacement de l'argument formel, une optimisation doit ici logiquement utiliser un bloc de mémoire appelé et fourni distinct pour le résultat de la fonction.

Cependant, un résultat de fonction qui n'est pas passé dans un registre est normalement fourni par l'appelant.C'est-à-dire ce que l'on pourrait raisonnablement appeler RVO, une sorte de RVO diminué, pour le cas d'un return expression qui dénote un argument formel, c'est ce qui se passerait de toute façon.Et cela ne correspond pas au texte « en construisant l’objet automatique directement dans la valeur de retour de la fonction ».

En résumé, le flux de données exigeant que l'appelant transmette une valeur signifie que c'est nécessairement l'appelant qui initialise le stockage d'un argument formel, et non la fonction.Par conséquent, la copie d’un argument formel ne peut être évitée. en général (ce terme fouine couvre les cas particuliers où le compilateur peut faire des choses très spéciales, en particulier pour le code machine en ligne).Cependant, c’est la fonction qui initialise le stockage de tout autre objet automatique local, et il n’y a alors aucun problème à faire du RVO.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top