web-dev-qa-db-fra.com

Les objets temporaires - quand sont-ils créés, comment les reconnaissez-vous dans le code?

Dans Eckel, Vol 1, pg: 367

//: C08:ConstReturnValues.cpp
// Constant return by value
// Result cannot be used as an lvalue
class X {
   int i;
public:
   X(int ii = 0);
   void modify();
};

X::X(int ii) { i = ii; }

void X::modify() { i++; }

X f5() {
   return X();
}

const X f6() {
   return X();
}

void f7(X& x) { // Pass by non-const reference
   x.modify();
}

int main() {
   f5() = X(1); // OK -- non-const return value
   f5().modify(); // OK
// Causes compile-time errors:
//! f7(f5());
//! f6() = X(1);
//! f6().modify();
//! f7(f6());
} ///:~

Pourquoi f5() = X(1) a-t-il réussi? Qu'est-ce qui se passe ici???

Q1. Quand il fait X(1) - que se passe-t-il ici? S'agit-il d'un appel de constructeur - Ne devrait-il pas lire alors X::X(1); S'agit-il d'une instanciation de classe - n'est-ce pas une instanciation de classe quelque chose comme: X a(1); Comment le compilateur détermine-t-il ce que X(1) est ?? Je veux dire ... la décoration du nom a lieu alors ... X(1) le constructeur L'appel se traduirait par quelque chose comme: globalScope_X_int en tant que fonction Name .. ???

Q2. Un objet temporaire est sûrement utilisé pour stocker l'objet résultant créé par X(1) Cet objet ne serait alors pas affecté à l'objet f5() renvoie (Qui serait également un objet temporaire)? Étant donné que f5() renvoie un objet temporaire Qui sera bientôt supprimé, comment peut-il affecter une constante temporaireà une autre constante temporaire ??? Quelqu'un pourrait-il expliquer clairement pourquoi: f7(f5()); devrait être remplacé temporairement et temporairement par un f5();

30
user621819

Toutes vos questions se résument à une règle en C++ qui dit qu'un objet temporaire (sans nom) ne peut pas être lié à une référence non const. (Parce que Stroustrup a estimé que cela pourrait provoquer des erreurs logiques ...)

Le seul problème, c'est que vous pouvez invoquer une méthode sur une base temporaire: alors X(1).modify() va bien, mais f7(X(1)) ne l'est pas.

Quant au lieu où le temporaire est créé, il s’agit du travail du compilateur. Les règles du langage précisent que le temporaire ne doit survivre que jusqu'à la fin (et non plus) de l'expression complète en cours, ce qui est important pour les instances temporaires de classes dont le destructeur a un effet secondaire.

Par conséquent, l'instruction suivante X(1).modify(); peut être entièrement traduite en:

{
    X __0(1);
    __0.modify();
} // automatic cleanup of __0

En gardant cela à l'esprit, nous pouvons attaquer f5() = X(1);. Nous avons ici deux temporaires et une mission. Les deux arguments de l'affectation doivent être entièrement évalués avant l'appel de l'affectation, mais l'ordre n'est pas précis. Une traduction possible est:

{
    X __0(f5());
    X __1(1);
    __0.operator=(__1);
}

( l'autre traduction est en train de permuter l'ordre dans lequel __0 et __1 sont initialisés )

Et la clé pour que cela fonctionne est que __0.operator=(__1) est une invocation de méthode, et les méthodes peuvent être invoquées sur des temporaries :)

31
Matthieu M.
  1. Il s’agit bien d’un appel de constructeur, d’une expression évaluant un objet temporaire de type X. Les expressions de la forme X([...]) avec X étant le nom d'un type sont des appels de constructeur qui créent des objets temporaires de type X (bien que je ne sache pas l'expliquer dans un langage standard approprié et qu'il existe des cas spéciaux dans lesquels l'analyseur peut se comporter différemment) . C'est la même construction que vous utilisez dans vos fonctions f5 et f6, en omettant simplement l'argument optionnel ii.

  2. Le temporaire créé par X(1) demeure (ne sera pas détruit/invalide) jusqu'à la fin de l'expression complète qui le contient, ce qui signifie généralement (comme dans ce cas avec l'expression d'affectation) jusqu'au point-virgule. De même, f5 crée-t-il une variable temporaire X et la renvoie-t-elle au site d'appels (à l'intérieur de la variable main), la copiant ainsi. Ainsi, dans l'appel principal, l'appel f5 renvoie également une variable X temporaire. La variable temporaire X créée par X(1) est ensuite affectée à cette variable temporaire X. Une fois cela fait (et le point-virgule atteint, si vous le souhaitez), les deux temporaires sont détruits. Cette affectation fonctionne parce que ces fonctions renvoient des objets non constants ordinaires, qu’ils soient simplement temporaires et détruits une fois que l’expression est pleinement évaluée (rendant ainsi l’affectation plus ou moins insensée, même si elle est parfaitement valide).

    Cela ne fonctionne pas avec f6 car cela renvoie un const X auquel vous ne pouvez pas affecter. De même, f7(f5()) ne fonctionne pas, puisque f5 crée un objet temporaire et que les objets temporaires ne se lient pas à des références à valeur constante non constantes X& (C++ 11 a introduit les références rvalue X&& à cette fin, mais c'est une histoire différente). Cela fonctionnerait si f7 prenait une référence constante const X&, car les références constantes de lvalue se lient aux temporaires (mais alors f7 ne fonctionnerait plus, bien sûr).

6
Christian Rau

Voici un exemple de ce qui se passe réellement lorsque vous exécutez votre code. J'ai apporté quelques modifications pour clarifier les processus en coulisses:

#include <iostream>

struct Object
{
    Object( int x = 0 ) {std::cout << this << ": " << __PRETTY_FUNCTION__ << std::endl;}
    ~Object() {std::cout << this << ": " << __PRETTY_FUNCTION__ << std::endl;}
    Object( const Object& rhs ){std::cout << this << ": " << __PRETTY_FUNCTION__ << " rhs = " << &rhs << std::endl;}
    Object& operator=( const Object& rhs )
    {
        std::cout << this << ": " << __PRETTY_FUNCTION__ << " rhs = " << &rhs << std::endl;
        return *this;
    }
    static Object getObject()
    {
        return Object();
    }
};

void TestTemporary()
{
    // Output on my machine
    //0x22fe0e: Object::Object(int) -> The Object from the right side of = is created Object();
    //0x22fdbf: Object::Object(int) -> In getObject method the Temporary Unnamed object is created
    //0x22fe0f: Object::Object(const Object&) rhs = 0x22fdbf -> Temporary is copy-constructed from the previous line object
    //0x22fdbf: Object::~Object() -> Temporary Unnamed is no longer needed and it is destroyed
    //0x22fe0f: Object& Object::operator=(const Object&) rhs = 0x22fe0e -> assignment operator of the returned object from getObject is called to assigne the right object
    //0x22fe0f: Object::~Object() - The return object from getObject is destroyed
    //0x22fe0e: Object::~Object() -> The Object from the right side of = is destroyed Object();

    Object::getObject() = Object();
}

Vous devez savoir que sur la plupart des compilateurs modernes, la construction de copie sera évitée. C'est parce que l'optimisation est faite (Optimisation de la valeur de retour) par le compilateur. Dans ma sortie, j'ai explicitement retiré l'optimisation pour montrer ce qui se passe réellement selon le standard. Si vous souhaitez également supprimer cette optimisation, utilisez l'option suivante: 

-fno-elide-constructors
1
stanimirco