2015-04-18 15 views
10

ja oglądając "Don’t Help the Compiler" Talk STL, gdzie ma podobny przykład na slajdzie 26:Dlaczego potrzebne jest wyraźne std :: move podczas zwracania kompatybilnego typu?

struct A 
{ 
    A() = default; 
    A(const A&) { std::cout << "copied" << std::endl; } 
    A(A&&) { std::cout << "moved" << std::endl; } 
}; 

std::pair<A, A> get_pair() 
{ 
    std::pair<A, A> p; 
    return p; 
} 

std::tuple<A, A> get_tuple() 
{ 
    std::pair<A, A> p; 
    return p; 
} 

std::tuple<A, A> get_tuple_moved() 
{ 
    std::pair<A, A> p; 
    return std::move(p); 
} 

z tym, następujące wezwanie:

get_pair(); 
get_tuple(); 
get_tuple_moved(); 

Wytwarza to wyjście:

moved 
moved 
copied 
copied 
moved 
moved 

See MCVE in action.

Wynik get_pair jest skonstruowany w ruchu, zgodnie z oczekiwaniami. Ruch może również zostać całkowicie wyeliminowany przez NRVO, ale nie wchodzi w zakres niniejszego pytania.

Wynik z get_tuple_moved jest również skonstruowany jako ruch, który jest jawnie określony, aby tak być. Jednak wynik get_tuple jest skonstruowany na zasadzie kopiowania, co jest dla mnie całkowicie nieoczywiste.

Pomyślałem, że każde wyrażenie przekazane do instrukcji return może być uważane za zawierające domyślnie move, ponieważ kompilator wie, że i tak wyjdzie poza zakres. Wygląda na to, że się mylę. Czy ktoś może wyjaśnić, co tu się dzieje?

Zobacz również podobne, ale różnią się pytanie: When should std::move be used on a function return value?

+0

Czy wyłączyłeś elizację kopii? Lepiej dodaj prawdziwy MCVE. – juanchopanza

+2

@juanchopanza W rzeczywistości jest to prawdziwe MCVE, po prostu umieść metody wywołujące w 'main()'. Zachowanie dla 'get_tuple' i' get_tuple_moved' jest takie samo niezależnie od RVO, podczas gdy wpływa na 'get_pair'. – Mikhail

+1

@Mikhail: tak właściwie to _nie jest prawdziwym MCVE (ponieważ wymaga więcej pracy niż kopiowanie i wklejanie, chociaż tylko odrobinę) :-) –

Odpowiedz

8

Instrukcja return w get_tuple() należy skopiować zainicjowany za pomocą Move-konstruktora, ale ponieważ typ zwracanego wyrażenia i typ zwracany nie match, konstruktor kopii zostaje wybrany zamiast tego. W C++ 14 nastąpiła zmiana, w której istnieje teraz początkowa faza przeciążenia, która traktuje zwrot jako wartość rwart, gdy jest po prostu zmienną automatyczną zadeklarowaną w ciele.

Odpowiednie sformułowanie można znaleźć w [class.copy]/p32:

Kiedy kryteria elizji operacji Kopiuj/przenieś są spełnione, [..] lub gdy wyraz w instrukcja return jest wyrażeniem id (prawdopodobnie nawiasowanym), które nazywa obiekt z automatycznym czasem zapisu zadeklarowanym w ciele [..], rozdzielczość przeciążania w celu wybrania konstruktora dla kopii jest najpierw wykonywana tak, jakby obiekt był oznaczony przez rvalue.

Więc w C++ 14 wszystkie dane wyjściowe powinny być pochodzących z move-konstruktora A.

wersjach bagażniku brzękiem i gcc już wdrożyć tę zmianę. Aby uzyskać takie samo zachowanie w trybie C++ 11, musisz użyć jawnego std :: move() w instrukcji return.

+0

Zasadniczo jest to niestandardowe zachowanie, które ma miejsce przynajmniej w wersjach 'gcc-4.9.2' i' Visual Studio 2015 CTP 6'? – Mikhail

+2

@Mikhail Jest to standardowe zachowanie w C++ 11, ale nie są zgodne z najnowszym standardem (C++ 14). – 0x499602D2

+0

Clang w rzeczywistości nie wprowadził jeszcze tej zmiany, ale GCC ma. – 0x499602D2

Powiązane problemy