2012-10-27 16 views
14

Załóżmy, że mamy tę klasę:C++ 11 konstruktor ruch nie nazywa, konstruktor domyślny preferowane

class X { 
public: 
    explicit X (char* c) { cout<<"ctor"<<endl; init(c); }; 
    X (X& lv) { cout<<"copy"<<endl; init(lv.c_); }; 
    X (X&& rv) { cout<<"move"<<endl; c_ = rv.c_; rv.c_ = nullptr; }; 

    const char* c() { return c_; }; 

private: 
    void init(char *c) { c_ = new char[strlen(c)+1]; strcpy(c_, c); }; 
    char* c_; 

}; 

i ten przykład użycia:

X x("test"); 
cout << x.c() << endl; 
X y(x); 
cout << y.c() << endl; 
X z(X("test")); 
cout << z.c() << endl; 

wyjście jest:

ctor 
test 
copy 
test 
ctor <-- why not move? 
test 

Używam VS2010 z ustawieniami domyślnymi. Spodziewałbym się, że ostatni obiekt (z) zostanie skonstruowany w ruchu, ale tak nie jest! Jeśli używam X z(move(X("test")));, ostatnie linie wyjścia to ctor move test, jak można się spodziewać. Czy jest to przypadek (N) RVO?

Q: Czy należy nakazać przeniesienie ctor zgodnie z normą? Jeśli tak, dlaczego tak się nie nazywa?

+2

To kopia elizja. Jeśli elizacja kopiowania nie powiodła się, nastąpiłby ruch. Dlaczego tytuł twojego postu mówi "preferowany konstruktor domyślny"? Nie jest wywoływany żaden domyślny konstruktor, a zamiast konstruktora ruchu nie jest preferowane żadne działanie. Jest całkowicie eliminowany. –

+0

Ten kod nie powinien się kompilować, ponieważ C++ 11; literał łańcuchowy nie może być niejawnie przekonwertowany na nieciągły "char *". –

Odpowiedz

19

To, co widzisz, to copy elision, co pozwala kompilatorowi bezpośrednio skonstruować tymczasowy obiekt docelowy, który ma zostać skopiowany/przeniesiony, a tym samym usunąć kopię (lub przenieść) parę konstruktora/destruktora. Sytuacje, w których kompilator może ubiegać się kopiowania elizja podano w §12.8.32 standardu C++ 11:

Kiedy pewne kryteria są spełnione, to implementacja wolno pominąć z Kopiuj/przenieś budowa obiektu klasy, nawet jeśli konstruktor i/lub destruktor obiektu copy/move dla obiektu mają skutki uboczne. W takich przypadkach implementacja traktuje źródło i cel pominiętej operacji kopiowania/przenoszenia jako dwa różne sposoby odwoływania się do tego samego obiektu, a zniszczenie tego obiektu ma miejsce później niż . obiekty zostałyby zniszczone bez optymalizacji . Ten elizja kopii/przenieść operacji, zwany kopia elizja, jest dozwolona w następujących okolicznościach (co w połączeniu może wyeliminować wiele kopii):

  • w instrukcji return w funkcji o typie klasy powrotnej, gdy określenie jest nazwą nieulotnej automatycznego obiektu z
    samo CV bez zastrzeżeń typu Internet red jako typ zwracanej przez funkcję, operacja
    kopii/ruch może zostać pominięte poprzez skonstruowanie automatycznego
    przedmiotu bezpośrednio do wartości powrotnej funkcyjnego
  • w rzut-ex pression, gdy operand jest nazwą nieulotnego automatycznego obiektu, którego zasięg nie wykracza poza koniec najgłębiej zamykającego try-bloku (jeśli istnieje), kopiowanie/przenoszenie operacji z operandu do wyjątku obiekt (15.1) można pominąć, tworząc obiekt automatyczny bezpośrednio pod obiekt wyjątku
  • , gdy obiekt klasy tymczasowej nie jest powiązany z odwołaniem (12.2) zostaną skopiowane/przeniesione do obiektu klasy z on samo cv-Bez zastrzeżeń typu fi kowane, operacja kopiowania/ruch może być pominięte przez budowy tymczasowego obiektu bezpośrednio do docelowej
    pominiętym kopii/przenieść
  • kiedy deklaracja wyjątku obsługi wyjątków (klauzula 15) deklaruje obiekt tego samego typu (z wyjątkiem cv-kwali fi kacji) jako obiekt wyjątku (15.1), operację kopiowania/przenoszenia można pominąć
    przez składanie deklaracji wyjątku jako alias dla obiektu wyjątku, jeśli znaczenie programu pozostanie niezmienione, z wyjątkiem wykonanie destruktory dla obiektu zadeklarowanego przez
    deklaracji wyjątku.
+1

Czy możesz podać prosty przykład bez użycia 'std :: move', który zmusiłby kompilator do użycia konstruktora ruchu? – emesx

+0

@elmes: Dlaczego chcesz wywołać konstruktor ruchu, gdy nie jest to konieczne? – Grizzly

+0

Chciałbym zobaczyć jakikolwiek przykład użycia go z klasą "X", po prostu wierzyć, że sens ma definiowanie jednego. – emesx

0

Telefonujesz X'schar* konstruktora X("test") jawny.

Dlatego też drukuje ctor

+1

A następnie wywołanie konstruktora ruchu X podczas deklarowania 'z'. – Xeo

3

Wyjście ctor masz w swojej trzeciej linii kodu jest do budowy tymczasowego obiektu. Po tym czasie tymczasowy zostanie przeniesiony do nowej zmiennej z. W takiej sytuacji kompilator może wybrać kopiowanie/przenoszenie i wygląda na to, że tak właśnie było.

Standardowe stany:

(§12.8/31) Kiedy pewne kryteria są spełnione, to implementacja wolno pominąć budowę kopia/ruch obiektu klasy, nawet jeśli kopia/konstruktora ruchu i/lub destruktor dla obiektu mają skutki uboczne. [...] Ta elizacja operacji kopiowania/przenoszenia, zwana copy elision, jest dozwolona w następujących okolicznościach (które można łączyć w celu wyeliminowania wielu kopii):
[...]
- gdy tymczasowy obiekt klasy, który nie został powiązany z odwołaniem (12.2) zostałby skopiowany/przeniesiony do obiektu klasy z tym samym typem bez cv, operacja kopiowania/przenoszenia może zostać pominięta przez skonstruowanie obiektu tymczasowego bezpośrednio do celu pominiętej kopii/przeniesienia
[...]

Jednym ważnym warunkiem jest to, że obiekt źródłowy i docelowy są tego samego typu (oprócz CV-kwalifikacji, czyli rzeczy jak const).

Dlatego jeden sposób można wymusić konstruktora ruch ma zostać wywołana jest połączenie inicjalizacji obiektu z niejawna konwersja typu:

#include <iostream> 

struct B 
{}; 

struct A 
{ 
    A() {} 
    A(A&& a) { 
    std::cout << "move" << std::endl; 
    } 
    A(B&& b) { 
    std::cout << "move from B" << std::endl; 
    } 
}; 


int main() 
{ 
    A a1 = A(); // move elided 
    A a2 = B(); // move not elided because of type conversion 
    return 0; 
} 
+0

Czy 'A :: A (B && b)' może być uważane za _move constructor_? Projekt N3936 (12.8.3) mówi: "Konstruktor bez szablonu dla klasy X jest konstruktorem ruchu, jeśli jego pierwszym parametrem jest typ X &&, const X &&, volatile X && lub const volatile X && i albo nie ma innych parametrów lub w przeciwnym razie wszystkie pozostałe parametry mają domyślne argumenty " –

Powiązane problemy