2015-04-05 10 views
5

schauen Sie bitte auf den folgenden Beispielcode:Warum wird der Kopierkonstruktor anstelle des Move-Konstruktors aufgerufen?

#include <iostream> 

    struct Foo { 
     Foo() { std::cout << "Default!\n"; } 
     Foo(const Foo& foo) { std::cout << "Copy!\n"; } 
     Foo(Foo&& foo) { std::cout << "Move!\n"; } 
    }; 

    struct Bar { 
     Foo foo; 
     Bar() {} 
     Bar(Bar &that) : foo(that.foo) {} 
     Bar(Bar &&that) : foo(std::move(that.foo)) {} 
    }; 

    Bar f() { 
     Bar bar; 
     return bar; 
    } 

    int main() { 
     Bar bar(f()); 
    } 

Ich bin davon aus, dass der Ausgang dieses Codes sein sollte:

Default! 
Move! 

aber was ich bekommen ist:

Default! 
Copy! 

Ich kann keinen Grund sehen, warum der Kopierkonstruktor anstelle des Move-Konstruktors aufgerufen wird. Wenn ich das Schlüsselwort const vor Bar &that in die Deklaration des Kopierkonstruktors von struct Bar setze, habe ich das richtige Ergebnis. Ich weiß, dass es für viele Fälle besser ist, einen Konstantenreferenzwert zu verwenden, als nur einen L-Wert-Bezugswert für Kopierkonstruktoren, aber ich möchte nur den Grund wissen, warum dies passiert ist.

Warum wurde in diesem Beispiel Bar & gegenüber Bar && bevorzugt, obwohl der Rückgabewert f() als Prvalue betrachtet werden sollte? Warum löst das Schlüsselwort const das Problem? Löst const wirklich das Problem? Hat es etwas mit RVO (Return Value Optimization) zu tun? Oder ist das nur ein Compiler Bug?

Ich habe dieses Beispiel auf Visual C++ November 2012 CTP getestet.

Ich habe ein ähnliches Problem hier:

Copy constructor is being called instead of the move constructor

Aber ich kann immer noch nicht verstehen, warum.

Kann mir jemand helfen?

+0

Kann nicht reproduziert werden. Mit GCC bekomme ich nur 'Default!' (Vernünftige NRVO bei der Arbeit), mit '-fno-elide-constructors' bekomme ich den erwarteten' Default! Bewegung! Bewege dich! '. Vielleicht ein Compiler b^Hshortcoming? –

+0

ändere 'Bar (Bar & this)' in 'Bar (const Bar & that)'. –

+0

@KerrekSB 1. Ich verstehe den Fall von NRVO. Aber warum zwei und nicht nur ein 'Move!' Erwartet wird, wenn der Kopierschutz ausgeschaltet ist? 2. Also sagst du, dass dies nur ein Fehler des Compilers ist, den ich benutzt habe? Danke trotzdem. –

Antwort

3

Wow, wenn ich dies mit kompilieren ...

  1. Visual Studio in Debug-I finden Sie unter "Standard! Kopieren Sie!".
  2. Visual Studio in Release sehe ich "Standard!".
  3. Wenn Sie Bar(Bar &that) zu ändern, dann "Standard! Move!"
  4. Schockierend, wenn Sie die Reihenfolge Bar(Bar &that) mit Bar(Bar &&that) (so dass der Umzug ctor zuerst definiert ist) dann sehen Sie tatsächlich "Default! Move!"
+1

Wow! Ich habe auch selbst 1, 2 und 3 getestet, aber 4 ist wirklich überraschend! Danke. Hast du eine Idee, warum solche Dinge passieren? –

+2

OK, # 4 stellt ziemlich schlüssig fest, dass dies ein Compilerfehler ist. –

+0

Um fair zu sein, sollte 'f()' 'std :: move (bar)' zurückgeben. – Mohammad

-2

Ihre Frage wird wahrscheinlich here beantwortet.

Ein temporäres Objekt kann nicht an eine nicht konstante Referenz gebunden werden. Der Kopierkonstruktor muss eine Referenz auf ein konstantes Objekt nehmen, um Kopien von temporären Objekten erstellen zu können.

Die andere Sache ist, dass temporäre Objekte nicht modifizierbar sein sollen, da sie in absehbarer Zeit zerstört werden sollen. Das Halten eines Verweises auf Provisorien führt zu einer möglichen Modifikation an nicht existierenden Objekten aus Unachtsamkeit.

+0

Dieses Zitat hat nichts mit dem beobachteten Verhalten zu tun. –

+1

@ T. C .: Dieses Zitat hat alles damit zu tun. Der Kopierkonstruktor sollte nicht nur eine schlechtere Übereinstimmung sein, er sollte nicht einmal Teil des Kandidatensatzes sein. –

+1

@BenVoigt Sicher, aber das erklärt nichts. Die Frage ist: "Warum wurde es genannt?", Nicht "Warum wurde es nicht genannt?" –

4

Es ist nur die übliche Nichteinhaltung von Visual C++, die das Binden einer nichtkonstanten Lvalue-Referenz zu einem temporären ermöglicht.Es verstößt gegen die Sprachregeln, aber es dauerte zu lange, bevor es abgefangen wurde, also gibt es Code, der von dem Fehler abhängt, der bei richtiger Behebung brechen würde.

Dieses Verhalten, das den Kopierkonstruktor nicht const versehentlich verwendet, kombiniert mit unvollständiger Rvalue-Referenzunterstützung in der Visual C++ - Version, führt offensichtlich dazu, dass die falsche Überladung ausgewählt wird.

Wenn Sie C++ 11/C++ 14 Funktionen verwenden möchten, bleiben Sie am besten auf der neuesten Visual C++ - Version.