2011-10-21 27 views
8

Nauczyłem się konstruktorów ruchu w ciągu ostatniego dnia, starając się trzymać ogólnej zasady zwracania przez wartość, jak większość ludzi sugeruje, i natknąłem się na interesującą ja) dylemat.RVO, operacje przeniesienia i dylemat

Załóżmy, że mam kosztowne skonstruowanie/skopiowanie klasy "C", która poprawnie zdefiniowała konstruktora kopii, operatora przypisania, konstruktora ruchu i operatora przeniesienia.

pierwsze, część kodu elides konstruktora kopii jak Przewidywana

C make_c1() { 
    return C(); 
} 

podobnie jak poniżej:

C make_c2() { 
    C tmp; 
    return tmp; 
} 

tak jak i w tym (czy przekazać w pozycji 1 lub 2) :

C make_c3(int a) { 
    return a == 1 ? make_c1() : make_c2(); 
} 

to kiedy się do tego, że mam problem:

C make_c4(int a) { 
    C tmp; 
    return a == 1 ? make_c1() : tmp; 
} 

Podanie 1 powoduje wybranie RVO dla wyniku make_c1, ale podanie wartości 2 powoduje uruchomienie konstruktora kopiowania w tmp.

zmianie funkcji na następujące wywołuje konstruktor posunięcie być wyzwalany przez tmp Zamiast:

C make_c5(int a) { 
    C tmp; 
    return a == 1 ? make_c1() : std::move(tmp); 
} 

Wszystko wielki i wspaniały z wyjątkiem ...

W tych prostych przykładach RVO został wywołany dość tak jak się spodziewałem.

Co jednak, jeśli mój kod jest nieco bardziej złożony i na niektórych kompilatorach nie wywołuje RVO w tej ostatniej funkcji? W takim przypadku będę musiał zawinąć moje wywołanie make_c1 w std :: move, co sprawi, że kod będzie mniej wydajny na tych kompilatorach, które wywołują RVO.

Więc moje pytania to:

  1. Dlaczego konstruktor ruch nie powoływać w make_c4 kiedy wróciłem mój lokalny obiekt? (W końcu zostanie zniszczony).
  2. W funkcji make_c5, czy powinienem zwrócić wyniki make_c1 według wartości lub przesuwając je? (Aby uniknąć różnych wersji kodu dla różnych kompilatorów/platform).
  3. Czy istnieje lepszy sposób kodowania funkcji końcowej tak, aby działał prawidłowo w przypadku uzasadnionej implementacji kompilatora?

Kompilator, z którym grałem, to GCC 4.5.3 na Cygwin.

Odpowiedz

7

Domniemane przeniesienie zwrotu jest legalne tylko w tych samych kontekstach, w których RVO jest legalne. RVO jest zgodne z prawem, gdy wyrażenie jest nazwą nieulotnego obiektu automatycznego (innego niż parametr funkcji lub parametru catch-clause) o tym samym typie nieuwzględnionym cv jak typ zwracany przez funkcję ([class.copy]/p31/b1).

Jeśli przekształcić make_c4 do:

C make_c4(int a) { 
    C tmp; 
    if (a == 1) 
     return make_c1(); 
    return tmp; 
} 

Potem dostajesz oczekiwaną budowę ruch na wezwanie do make_c4(2).Twój przepisany make_c5 nie jest pożądany z dokładnie podanych przez ciebie powodów.

Update:

powinien mieć włączone także odniesienie do [expr.cond]/P6/B1 który opisuje semantykę ekspresji warunkowej gdy drugie wyrażenie jest prvalue a trzeci lwartością , ale oba mają ten sam typ:

Drugi i trzeci operand mają ten sam typ; wynikiem tego jest tego typu. Jeśli operandy mają typ klasy, wynikiem jest tymczasowy typ wyniku, który jest zainicjalizowany w postaci kopii z albo drugiego argumentu lub trzeciego argumentu, w zależności od wartości pierwszego argumentu operacji .

tj. ten akapit określa, że ​​wynikowa wartość parametru warunkowego to zainicjowana metodą kopiowania, z 3. argumentu w twoim przykładzie. Inicjalizacja kopii jest zdefiniowana w [dcl.init]/p14. Gdy źródłem inicjalizacji kopii jest l-klasa typu klasy, wywoła to konstruktor kopiowania typu. Jeśli źródło jest rwartością, wywoła konstruktor ruchu, jeśli taki istnieje, inaczej wywoła konstruktor kopiowania.

Specyfikacja wyrażenia warunkowego nie ma żadnego zastosowania dla niejawnego ruchu z argumentu lwartości, nawet jeśli wyrażenie warunkowe jest częścią wyrażenia zwrotnego. Możliwe, że język mógł zostać stworzony tak, aby umożliwić taki niejawny ruch, ale o ile wiem, nigdy nie został zaproponowany. Ponadto istniejąca specyfikacja wyrażenia warunkowego jest już bardzo skomplikowana, co sprawia, że ​​taka zmiana języka jest tym trudniejsza.

+0

Nie jestem pewien, czy to odpowiada na wszystkie moje pytania, mimo że "naprawia" konkretny kod, z którym gram. Dlaczego twoja forma 2 zwrotów działa, gdy moja forma nie? Czy to jest problem z kompilacją? Sądzę, że częścią mojego problemu jest to, że RVO jest całkowicie zależne od kompilatora, podczas gdy poruszanie się jest pod moją kontrolą. –

+0

@ IanM_Matrix1: nie powinieneś polegać na konkretnej optymalizacji, aby uzyskać krytyczny przyrost wydajności, ponieważ jest to tajemna nauka i nie można wiarygodnie przewidzieć, kiedy zostanie uruchomiona, czy nie. To właśnie nas "przenosi": zdolność kontrolowania w pewien sposób tego, co się dzieje. Jeśli twoja klasa jest droga do skopiowania, pomyślałabym o całkowitym wyłączeniu kopii, aby zapobiec przypadkowemu użyciu. –

+0

Całkowicie się z tym zgadzam, ale czasami kopiowanie jest konieczne ** i ** kosztowne, chociaż funkcja typu klonowego dałaby lepszą kontrolę nad tym. W każdym razie wygląda na to, że na moje pytania odpowiedziano najnowszą aktualizacją Howarda - 1 odpowiada na ostatnią aktualizację, 2 odpowiada "nie", a 3 odpowiada oryginalnym kodem. Dzięki Howard :) –