2013-04-15 23 views
9

Używam Visual Studio 2012 Update 2 i mam problem ze zrozumieniem, dlaczego std :: vector próbuje użyć konstruktora kopiowania unique_ptr. Przyjrzałem się podobnym problemom, a większość dotyczy braku konstruktora ruchu i/lub operatora.Move Semantics with unique_ptr

Jeśli zmienię zmienną składową na ciąg, mogę sprawdzić, czy wywoływany jest konstruktor ruchu; jednak próba użycia unique_ptr powoduje błąd kompilacji:

error C2248: 'std::unique_ptr<_Ty>::unique_ptr' : cannot access private member declared in class 'std::unique_ptr<_Ty>'.

Mam nadzieję, że ktoś może wskazać mi to, czego mi brakuje, dzięki!

#include <vector> 
#include <string> 
#include <memory> 

class MyObject 
{ 
public: 
    MyObject() : ptr(std::unique_ptr<int>(new int)) 
    { 
    } 

    MyObject(MyObject&& other) : ptr(std::move(other.ptr)) 
    { 
    } 

    MyObject& operator=(MyObject&& other) 
    { 
     ptr = std::move(other.ptr); 
     return *this; 
    } 

private: 
    std::unique_ptr<int> ptr; 
}; 

int main(int argc, char* argv[]) 
{ 
    std::vector<MyObject> s; 
    for (int i = 0; i < 5; ++i) 
    { 
     MyObject o; 
     s.push_back(o); 
    } 

    return 0; 
} 
+1

Jeśli chcesz zbudować swoje obiekty bezpośrednio w wektorze, można też po prostu 'emplace' nich:' for (int i = 0; i <5; ++ i) s.emplace_back(); 'To powinno działać również z VC11. –

Odpowiedz

11

Funkcja przyjmuje argument według wartości. Dlatego podejmowana jest próba kopiowania-konstruowania argumentu push_back() (jeśli podajesz wartość l), lub konstruowania ruchu (jeśli przekazujesz wartość r).

W tym przypadku o jest l-wartością - ponieważ nazwane obiekty są lwartościami - a wartości referencyjne rvalue nie mogą wiązać się z wartościami l. Dlatego kompilator nie może wywołać konstruktora ruchu.

Aby mieć swój przedmiot przeniósł, trzeba napisać:

s.push_back(std::move(o)); 
//   ^^^^^^^^^ 

Co mnie dziwi w tym przypadku jest to, że wydaje się VC11 generowane kopiowaniem konstruktora dla MyObject niejawnie nie definiując go jako usunięto (sądząc po błędzie, który napisałeś). Tak nie powinno być, ponieważ twoja klasa deklaruje konstruktor ruchu. Zgodnie z pkt 12.8/7 o C++ 11 Standard, w rzeczywistości:

If the class definition does not explicitly declare a copy constructor, one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy constructor is defined as deleted; otherwise, it is defined as defaulted (8.4)

muszę stwierdzić, że o ile błąd otrzymujesz jest poprawna - bo nie przechodzą rvalue do push_back() - VC11 nie jest w pełni zgodny tutaj.

+0

Świetnie! Nie wiedziałem o tym. Nadal jestem trochę zdezorientowany, dlaczego konstruktor ruchu zostaje wywołany tylko przez zmianę wartości unique_ptr na ciąg znaków. Jeśli VC11 generuje niejawny konstruktor kopii, oczekiwałbym, że będzie on używany, ponieważ nie użyłem std :: move. – zYzil

+1

@zYzil: Prawidłowe zachowanie nie polega na wywołaniu konstruktora kopiowania ani konstruktora ruchu 'std :: string' (patrz [ten przykład na żywo] (http://liveworkspace.org/code/3kW04t$377)), ponieważ 'MyObject' jest niekopiowalne i nie ruszasz się z niego. Jeśli twój kod wygląda * dokładnie * tak jak mój przykład (w tym brakujący 'std :: move()'), to VC11 ma błąd. –

+0

Zdecydowanie uważam, że jest błąd w VC11. [Ten przykład] (http://liveworkspace.org/code/1edTJY$3) nie kompiluje się na GCC 4.8.0; jednak w VC11 kompiluje i wyprowadza "Move ctor" z powodu wywołania konstruktora ruchu. Dzięki za pomoc! – zYzil

4

MyObject o; definiuje o jako obiekt. Co oznacza, że ​​jest to wartość l. Wykonanie s.push_back(o); następnie wywołuje przeciążenie wartości l push_back() (nie ma innego wyboru), które próbuje utworzyć kopię.

Ponieważ klasa jest noncopyable, trzeba ruchu obiektu do wektora:

for (int i = 0; i < 5; ++i) 
{ 
    MyObject o; 
    s.push_back(std::move(o)); 
}