9 Stimmen

Woher weiß der Compiler, dass er lokale Variablen verschieben muss?

Ich bin neugierig, wie diese Funktion genau funktioniert. Denken Sie an etwas wie

std::unique_ptr<int> f() { std::unique_ptr<int> lval(nullptr); return lval; }

Dieser Code lässt sich auch bei einem reinen Verschiebetyp gut kompilieren, da der Compiler ihn implizit verschiebt. Aber logischerweise wäre die Bestimmung, ob sich das Ergebnis auf eine lokale Variable bezieht oder nicht, für jeden Rückgabeausdruck die Lösung des Halting-Problems - und wenn der Compiler einfach alle lokalen Variablen als rWerte im Rückgabeausdruck behandeln würde, dann wäre dies problematisch, da die Variable in diesem einen Ausdruck mehrfach referenziert werden kann. Selbst wenn eine lokale Variable nur einen direkt Verweises können Sie nicht beweisen, dass er nicht noch andere indirekte Aliasnamen hat.

Woher weiß der Compiler also, wann er den Return-Ausdruck verlassen muss?

10voto

Xeo Punkte 126280

Es gibt eine einfache Regel: Wenn die Bedingungen für Copy Elision erfüllt sind (mit Ausnahme der Tatsache, dass die Variable ein Funktionsparameter sein kann), ist sie als rWert zu behandeln. Wenn das nicht gelingt, als lvalue behandeln. Andernfalls als lvalue behandeln.

§12.8 [class.copy] p32

Wenn die Kriterien für die Eliminierung eines Kopiervorgangs erfüllt sind oder erfüllt wären, außer dass das Quellobjekt ein Funktionsparameter ist, und das zu kopierende Objekt durch einen l-Wert bezeichnet ist, wird die Überladungsauflösung zur Auswahl des Konstruktors für die Kopie zunächst so durchgeführt, als ob das Objekt durch einen r-Wert bezeichnet wäre . Schlägt die Überladungsauflösung fehl oder ist der Typ des ersten Parameters des ausgewählten Konstruktors keine rvalue-Referenz auf den Typ des Objekts (möglicherweise cv-qualifiziert), wird die Überladungsauflösung erneut durchgeführt, wobei das Objekt als lvalue betrachtet wird. [ Nota: Diese zweistufige Überlastungsauflösung muss unabhängig davon durchgeführt werden, ob es zu einer Elision der Kopie kommt. Sie bestimmt den Konstruktor, der aufgerufen werden soll, wenn keine Elision erfolgt, und der gewählte Konstruktor muss zugänglich sein, auch wenn der Aufruf elidiert wird. -Ende Anmerkung ]

template<class T>
T f(T v, bool b){
  T t;
  if(b)
    return t; // automatic move
  return v; // automatic move, even though it's a parameter
}

Ich persönlich bin mit dieser Regel nicht einverstanden, da der folgende Code keine automatische Bewegung vorsieht:

template<class T>
struct X{
  T v;
};

template<class T>
T f(){
  X<T> x;
  return x.v; // no automatic move, needs 'std::move'
}

Siehe auch Diese Frage von mir .

5voto

In der Norm heißt es nicht "verweist auf eine lokale Variable", sondern "ist der Name eines nichtflüchtigen automatischen Objekts".

§12.8 [class.copy] p31

[...] Diese Eliminierung von Kopier-/Verschiebevorgängen, genannt Kopierelimination ist unter den folgenden Umständen zulässig [...]:

  • in einem return Anweisung in einer Funktion mit einem Klassenrückgabetyp, wenn der Ausdruck der Name eines nichtflüchtigen automatischen Objekts ist [...]

Zeiger oder Verweise sind also nicht erlaubt. Wenn man es auf eine böse Art und Weise liest, die eine Interpretation ausnutzen will, die wahrscheinlich nicht beabsichtigt war, kann man sagen, dass es bedeutet, Folgendes zu bewegen

struct A {
  std::unique_ptr<int> x;
  std::unique_ptr<int> f() { return x; }
};

int main() { A a; a.f(); }

In diesem Fall ist der Rückgabeausdruck der Name einer Variablen mit automatischer Speicherdauer. Einige andere Absätze in der Norm können auf mehrere Arten interpretiert werden, aber die Regel ist, die Interpretation zu nehmen, die am wahrscheinlichsten beabsichtigt ist.

3voto

Aber logischerweise wäre für jeden Rückgabeausdruck die Feststellung, ob sich das Ergebnis auf eine lokale Variable bezieht oder nicht, die Lösung des Halting-Problems.

Sie überbewerten die Komplexität des Problems. Der Compiler sieht natürlich, ob der Rückgabeausdruck eine der lokalen Variablen erwähnt, und er muss alle diese Variablen im Auge behalten, um die Destruktoren überhaupt aufrufen zu können. Beachten Sie, dass er nur wenn In der Rückgabe wird die Variable explizit erwähnt; wenn Sie einen Zeiger oder eine Referenz auf eine lokale Variable zurückgeben, muss dies nicht der Fall sein:

std::unique_ptr<int>& same( std::unique_ptr<int>& x ) { return x; }
std::unique_ptr<int> foo() {
   std::unique_ptr<int> p( new int );
   std::unique_ptr<int>& r = same(p);
   return r;                           // FAIL
}

2voto

Matthieu M. Punkte 266317

Ich glaube, Sie überschätzen hier die Kapazität des Compilers.

Wenn Sie eine lokale Variable direkt zurückgeben, ist die Aufgabe einfach: Sie können sie verschieben.

Wenn Sie einen Ausdruck aufrufen, bei dem eine lokale Variable verwendet werden soll, müssen Sie diese manuell angeben.

Voir hier einige Beispiele .

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X