2012-02-22 14 views
9

W tym momencie pisanie konstruktora kopiowania i pary operatorów przypisania jest dobrze zdefiniowane; szybkie wyszukiwanie doprowadzi Cię do wielu trafień, jak poprawnie je zakodować.Jaki jest "prawidłowy" sposób zapisu Copy/Move/operator = trio w C++ 11?

Teraz, gdy konstruktor ruchu wszedł w miks, czy istnieje nowy "najlepszy" sposób?

+1

Było dobre [pytanie SO] (http://stackoverflow.com/questions/9322174/move-assignment-operator- i-if-this-rhs), na które warto spojrzeć. –

+1

To pytanie jest zbyt szerokie. Musisz oderwać go do określonego scenariusza. Ponieważ nie ma przepisu, w jaki sposób napisać konstruktor kopiowania i operator przypisania w "dobrze określony" sposób dla każdej klasy. To samo dotyczy twojego pytania. –

+0

Dlaczego jest zbyt szeroki? Istnieje ogólnie przyjęty wzorzec dla konstruktora kopiowania i operatora przypisania, dlaczego nie dla konstruktora ruchu? – moswald

Odpowiedz

12

Korzystnie, oni po prostu być = default;, ponieważ typy członkowskie powinny mieć typów zasobów zarządzania, które ukrywają szczegóły przejść od ciebie, jak std::unique_ptr. Tylko wykonawcy tych typów "niskiego poziomu" powinni sobie z tym poradzić.

Pamiętaj, że musisz tylko przeszkadzać w semantyce ruchu, jeśli posiadasz zewnętrzny zasób (do obiektu). Jest zupełnie bezużyteczny w przypadku typów "płaskich".

+1

To jest ważny punkt. Przypuszczam, że mógłbym zakwalifikować się z "jeśli masz zamiar napisać własną", ale twoja odpowiedź jest bardzo poprawna dla ludzi, którzy nie zdają sobie sprawy, że nie muszą tego robić. Jednak w moim przypadku trzymam zewnętrzny zasób, który jest źródłem tego pytania. :) – moswald

+1

Drobne refaktoryzacje prawdopodobnie wyeliminują ten problem. Pomyślę o tym. Nie wiem, czy efekt końcowy będzie prostszy. – moswald

+0

Niestety, nie wszystkie kompilatory generują domyślny operator konstruktora ruchu/przypisania. VC2010 nie, nie wiem o VC2011. – lapk

2

Oto, co wymyśliłem, ale nie wiem, czy istnieje bardziej optymalne rozwiązanie.

class MyClass 
{ 
    void Swap(MyClass &other) 
    { 
     std::swap(other.member, member); 
    } 

public: 
    MyClass() 
     : member() 
    { 
    } 

    MyClass(const MyClass &other) 
     : member(other.member) 
    { 
    } 

    MyClass(MyClass &&other) 
     : member(std::move(other.member)) 
    { 
    } 

    MyClass &operator=(MyClass other) 
    { 
     other.Swap(*this); 
     return *this; 
    } 

private: 
    int member; 
}; 
+0

Dlaczego chcesz zamienić na prywatny? – ronag

+0

@ronag: w mojej klasie realnej, nie ma takiej potrzeby (jeszcze). Wolałbym nie ujawniać interfejsu API, który może pojawić się później i używać, prawdopodobnie niepoprawnie (chociaż nie wiem, w jaki sposób mogą nadużyć coś tak prostego jak zamiana). – moswald

+4

Jeśli 'MyClass' naprawdę ma mieć te semantyki, jest to prawdopodobnie najgorszy (najbiedniejszy) sposób, w jaki można napisać specjalnych członków, a mimo to mieć go za prawidłowy. Przepraszam, jestem taki bezpośredni, ale pomyślałem, że powinieneś wiedzieć. –

5

Najlepszym sposobem jest pozwolić kompilatorowi generować je wszystkie. Było to również najlepsze podejście w C++ 03 i jeśli udało ci się to zrobić, twoje klasy C++ 03 automatycznie stają się "przenoszone" po migracji do C++ 11.

Większość problemów związanych z zarządzaniem zasobami można rozwiązać, pisząc tylko niekopiowane konstruktory i destruktory klas zarządzających jednym zasobem, a następnie używając tylko tych klas złożonych oraz inteligentne wskaźniki (np. std::unique_ptr) i klasy kontenerów, aby budować bogatsze obiekty .

4

Korzystanie clang/libC++:

#include <chrono> 
#include <iostream> 
#include <vector> 

#if SLOW_DOWN 

class MyClass 
{ 
    void Swap(MyClass &other) 
    { 
     std::swap(other.member, member); 
    } 

public: 
    MyClass() 
     : member() 
    { 
    } 

    MyClass(const MyClass &other) 
     : member(other.member) 
    { 
    } 

    MyClass(MyClass &&other) 
     : member(std::move(other.member)) 
    { 
    } 

    MyClass &operator=(MyClass other) 
    { 
     other.Swap(*this); 
     return *this; 
    } 

private: 
    int member; 
}; 

#else 

class MyClass 
{ 
public: 
    MyClass() 
     : member() 
    { 
    } 

private: 
    int member; 
}; 

#endif 

int main() 
{ 
    typedef std::chrono::high_resolution_clock Clock; 
    typedef std::chrono::duration<float, std::milli> ms; 
    auto t0 = Clock::now(); 
    for (int k = 0; k < 100; ++k) 
    { 
     std::vector<MyClass> v; 
     for (int i = 0; i < 1000000; ++i) 
      v.push_back(MyClass()); 
    } 
    auto t1 = Clock::now(); 
    std::cout << ms(t1-t0).count() << " ms\n"; 
} 

$ clang++ -stdlib=libc++ -std=c++11 -O3 -DSLOW_DOWN test.cpp 
$ a.out 
519.736 ms 
$ a.out 
517.036 ms 
$ a.out 
524.443 ms 

$ clang++ -stdlib=libc++ -std=c++11 -O3 test.cpp 
$ a.out 
463.968 ms 
$ a.out 
458.702 ms 
$ a.out 
464.441 ms 

To wygląda jak około 12% różnicy prędkości na tym teście.

Objaśnienie: Jedna z tych definicji ma trywialny konstruktor kopiowania i operator przypisania kopiowania. Drugi nie. "Trivial" ma prawdziwe znaczenie w C++ 11. Oznacza to, że implementacja może używać klasy memcpy do kopiowania. Lub nawet skopiować duże tablice twojej klasy. Więc najlepiej jest sprawić, by twoi wyjątkowi członkowie byli banalni, jeśli możesz. Oznacza to, że pozwala to kompilatorowi je zdefiniować. Chociaż nadal możesz je zadeklarować pod numerem = default, jeśli wolisz.

+0

Howard, 'SLOW_DOWN' jest po prostu bardzo nieefektywną wersją. Nie chodzi nawet o porównanie z konstruktorami generowanymi przez kompilator itp. ... Oto dwa kody na ideone.com: [pierwszy] (http://ideone.com/WQVXK) używa wersji OP 'T & operator (T)', [ drugi] (http://ideone.com/SXgVN) używa jawnie zdefiniowanych 'T & (T &&)' i 'T & operator = (T &&)'. Porównaj dane wyjściowe między '-----------' ... – lapk

+1

@AzzA: Właśnie porównuję rozwiązanie OP do tego, które moim zdaniem jest lepsze. Nic dodać nic ująć. –

Powiązane problemy