2011-08-16 12 views
11

próbuję umieścić idiom kopiowaniem i swap do wielokrotnego użytku wstawek:ponowne idiom kopiowaniem i-swap

template<typename Derived> 
struct copy_and_swap 
{ 
    Derived& operator=(Derived copy) 
    { 
     Derived* derived = static_cast<Derived*>(this); 
     derived->swap(copy); 
     return *derived; 
    } 
}; 

zamierzam go mieszać w CRTP poprzez:

struct Foo : copy_and_swap<Foo> 
{ 
    Foo() 
    { 
     std::cout << "default\n"; 
    } 

    Foo(const Foo& other) 
    { 
     std::cout << "copy\n"; 
    } 

    void swap(Foo& other) 
    { 
     std::cout << "swap\n"; 
    } 
}; 

jednak proste badanie pokazuje, że to nie działa:

Foo x; 
Foo y; 
x = y; 

ten drukuje tylko „default” dwa razy, ale ani „kopia”, ani „wymiany” jest drukowana. Czego tu mi brakuje?

+1

Może kompilator dostarcza własną wersję 'operatora =' skoro jesteś brakuje jednego w klasie 'Foo'? Być może będziesz musiał zrobić 'Foo :: operator =() {return copy_and_swap();}' (pseudokod)? – RedX

Odpowiedz

0

Obawiam się, że jest to jeden obszar, w którym konieczne jest makro, ze względu na skomplikowane reguły dotyczące automatycznie generowanych kopii i operatorów przypisania.

Bez względu na to, co robisz, jesteś w jednym z dwóch przypadków:

  • podałeś (jawnie) deklaracji operatora przypisania, w tym przypadku oczekuje się przedstawić definicję zbyt
  • Nie podano (jawnie) deklaracji operatora przypisania, w którym to przypadku kompilator wygeneruje jeden, jeśli klasy podstawowe i mają jeden dostępny element.

Następne pytanie brzmi: Czy warto zautomatyzować takie pisanie?

Funkcja kopiowania i zamiany jest używana tylko w ściśle określonych klasach. Nie sądzę, żeby było warto.

1

Nie można dziedziczyć operatorów przypisania w specjalnym przypadku, jeśli pamięć działa poprawnie. Uważam, że mogą być wyraźnie podane w razie potrzeby.

Należy również zachować ostrożność w przypadku nadmiernego użycia funkcji kopiowania i zamiany. Daje nieidealne wyniki, gdy oryginał ma zasoby, które można ponownie wykorzystać do wykonania kopii, takie jak pojemniki. Bezpieczeństwo jest gwarantowane, ale optymalna wydajność nie jest.

+0

Dodanie 'using copy_and_swap :: operator =;' in 'struct Foo', nie poprawia sytuacji. Zobacz odpowiedź [Alexandre C.] (http://stackoverflow.com/questions/7080137/reusing-the-copy-and-swap-idiom/7080212#7080212). –

7

to:

Derived& operator=(Derived copy) 

nie ma zadeklarować operatora przypisania kopia dla klasy bazowej (to ma niewłaściwy podpis). Tak więc domyślny wygenerowany operator przypisania w Foo nie użyje tego operatora.

Pamiętaj 12,8:

zadeklarowana przez użytkownika kopia operator przypisania X :: operator = jest non-static non-szablon funkcja członkiem klasy X z dokładnie jeden parametr typu X, X & , const X &, lotny X & lub niestabilny X &.) [Uwaga: przeciążony operator przypisania musi być zadeklarowany jako posiadający tylko jeden parametr ; patrz 13.5.3. ] [Uwaga: operator może zadeklarować więcej niż jedną formę przydziału egzemplarza dla klasy. ] [Uwaga: w przypadku klasy X ma tylko kopiowania operatorowi przypisanie z parametrem typu X & wyrazem typu const X nie może być przypisane do obiektu typu X.

EDIT don „t to zrobić (można zobaczyć, dlaczego?):

można zrobić:

template<typename Derived> 
struct copy_and_swap 
{ 
    void operator=(const copy_and_swap& copy) 
    { 
     Derived copy(static_cast<const Derived&>(copy)); 
     copy.swap(static_cast<Derived&>(*this)); 
    } 
}; 

ale można stracić potencjalnego optymalizacji kopia elizja.

W rzeczywistości przypisze to dwa razy elementy klas pochodnych: jeden raz przez operatora przypisania copy_and_swap<Derived>, a jeden raz przez klasę pochodną "wygenerowany operator przypisania".Aby poprawić tę sytuację, trzeba by zrobić (i nie zapomnij zrobić):

struct Foo : copy_and_swap<Foo> 
{ 

    Foo& operator=(const Foo& x) 
    { 
     static_cast<copy_and_swap<Foo>&>(*this) = x; 
     return *this; 
    } 

private: 
    // Some stateful members here 
} 

Morał z tej historii: nie napisać klasę CRTP do kopiowania i wymiany idiomu.

+0

Co się stanie, jeśli '= usuniesz' operator generujący wygenerowany przez kompilator dla Foo (przy założeniu C++ 0x)? –

+0

'= delete' zabrania wywoływania' operator = 'na Foo, co przynosi efekt przeciwny do zamierzonego. –

+0

Czy krojenie problemu, który doprowadził do Twojej edycji? –

0

Kompilator automatycznie generuje operator przypisania kopii dla Foo, ponieważ nie ma go. Jeśli dodać

using copy_and_swap<Foo>::operator=; 

foo pojawi się błąd informujący o dwuznaczności na g ++.

+0

Próbowałem tego i to nie daje żadnej dwuznaczności. Jednak operator nadal nie jest wywoływany. –

+0

@ André Caron: Sprawdziłem to jeszcze raz: g ++ nie ma msvc. – mmmmmmmm

0

Być może można przerobić tak to wygląda tak:

template<class Derived> 
struct CopySwap 
{ 
    Dervied &operator=(Derived const &other) 
    { 
    return AssignImpl(other); 
    } 

    Derived &operator=(Dervied &&other) 
    { 
    return AssignImpl(std::move(other)); 
    } 

private: 
    Derived &AssignImpl(Derived other) 
    { 
    auto self(static_cast<Derived*>(this)); 
    self->swap(other); 
    return *self; 
    } 
}; 

To pewnie wszystko się inlined i prawdopodobnie nie będzie żadnych wolniej niż w oryginalnym kodzie.

0

To naprawdę nie odpowiedzieć na pytanie (@Alexandre C. already did), ale jeśli odwrócić dziedziczenia, można zrobić to działa:

template<typename Base> 
struct copy_and_swap : Base 
{ 
    copy_and_swap& operator=(copy_and_swap copy) 
    { 
     swap(copy); 
     return *this; 
    } 
}; 

struct Foo_ 
{ 
    Foo_() 
    { 
     std::cout << "default\n"; 
    } 

    Foo_(const Foo_& other) 
    { 
     std::cout << "copy\n"; 
    } 

    void swap(Foo_& other) 
    { 
     std::cout << "swap\n"; 
    } 
}; 

typedef copy_and_swap<Foo_> Foo; 

int main() 
{ 
    Foo x; 
    Foo y; 
    x = y; 
} 
Powiązane problemy