Rule-of-Three staje się Rule-of-Five z C++11?

Więc po obejrzeniu tego wspaniałego wykładu na temat referencji rvalue, pomyślałem, że każda klasa skorzysta z takiego "konstruktora ruchu", template<class T> MyClass(T&& other) edytuj i oczywiście" operator przypisania ruchu", template<class T> MyClass& operator=(T&& other) jak wskazuje Philipp w swojej odpowiedzi, jeśli ma dynamicznie przydzielanych członków, lub ogólnie przechowuje wskaźniki. Tak jak ty powinieneś mieć copy-ctor, operator przypisania i destruktor, jeśli dotyczą wymienione wcześniej punkty. Myśli?

Author: Flexo, 2011-01-24

8 answers

Powiedziałbym, że reguła trzech staje się regułą trzech, czterech i pięciu:

Każda klasa powinna jednoznacznie zdefiniować dokładnie jedną z poniższego zestawu członków specjalnych funkcje:

  • Brak
  • Destruktor, Konstruktor kopiujący, operator przypisania kopii

Dodatkowo, każda klasa, która jawnie definiuje Destruktor, może jawnie zdefiniować konstruktor move i / lub operator przypisania move.

Zazwyczaj, jeden z następujących zestawy członków specjalnych funkcje są sensowne:

  • None (dla wielu prostych klas, w których domyślnie generowane Funkcje specjalne są poprawne i szybkie)
  • Destruktor, Konstruktor kopiujący, Operator kopiowania (w tym przypadku klasa nie będzie ruchoma)
  • Destructor, move constructor, move assignment operator (w tym przypadku klasa nie będzie kopiowalna, przydatna dla klas zarządzających zasobami, gdzie zasób nie jest copyable)
  • Destruktor, Konstruktor kopiujący, Operator kopiowania przypisania, konstruktor move (ze względu na eliminację kopiowania, nie ma narzutu, jeśli operator kopiowania przypisania bierze swój argument przez wartość)
  • Destruktor, Konstruktor kopiujący, Operator kopiowania, konstruktor ruchu, operator przypisania ruchu

Zauważ, że konstruktor move i operator przypisania move nie będą generowane dla klasy, która jawnie deklaruje którykolwiek z pozostałych członków specjalnych funkcje, że Konstruktor kopiujący i operator kopiowania nie będą generowane dla klasy, która jawnie deklaruje konstruktor move lub operator move assignment, oraz że klasa z jawnie zadeklarowanym destruktorem i niejawnie zdefiniowanym konstruktorem kopiującym lub niejawnie zdefiniowanym operatorem kopiowania jest uważana za przestarzałą. W szczególności, następujące doskonale poprawne C++03 polimorficzna klasa bazowa

class C {
  virtual ~C() { }   // allow subtype polymorphism
};

Należy przepisać w następujący sposób:

class C {
  C(const C&) = default;               // Copy constructor
  C(C&&) = default;                    // Move constructor
  C& operator=(const C&) & = default;  // Copy assignment operator
  C& operator=(C&&) & = default;       // Move assignment operator
  virtual ~C() { }                     // Destructor
};
Trochę irytujące, ale prawdopodobnie lepiej niż alternatywa (automatyczne generowanie wszystkich specjalnych funkcji członowych).

W przeciwieństwie do reguły Wielkiej Trójki, gdzie nieprzestrzeganie tej reguły może spowodować poważne szkody, brak jednoznacznej deklaracji konstruktora ruchu i operatora przypisania ruchu jest ogólnie dobry, ale często nieoptymalny pod względem wydajności. Jak wspomniano powyżej, operatory move constructor I move assignment są generowane tylko wtedy, gdy nie ma jawnie zadeklarowanego konstruktora kopiującego, copy operator przydziału lub Destruktor. Nie jest to symetryczne do tradycyjnego zachowania C++03 w odniesieniu do automatycznego generowania konstruktora kopiującego i operatora przypisywania kopii, ale jest znacznie bezpieczniejsze. Tak więc możliwość definiowania konstruktorów move i operatorów move assignment jest bardzo przydatna i stwarza nowe możliwości( klasy czysto ruchome), ale klasy, które stosują się do zasady C++03 Wielkiej Trójki, nadal będą w porządku.

Dla klas zarządzających zasobami można zdefiniować Konstruktor kopiujący i operator kopiowania jako skasowany (który liczy się jako definicja), jeśli nie można skopiować zasobu bazowego. Często nadal potrzebujesz konstruktora ruchu i operatora przydziału ruchu. Operatory przypisania Copy I move są często implementowane przy użyciu swap, jak w C++03. Jeśli masz konstruktor move i operator move assignment, specializing std::swap stanie się nieistotny, ponieważ ogólny std::swap używa konstruktora move i operatora move assignment, jeśli jest dostępny, a to powinno być szybkie wystarczy.

Klasy, które nie są przeznaczone do zarządzania zasobami (tzn. brak niepustego destruktora) lub polimorfizm podtypu (tzn. brak Wirtualnego destruktora), nie powinny deklarować żadnej z pięciu specjalnych funkcji Członkowskich; wszystkie będą automatycznie generowane i zachowują się poprawnie i szybko.

 296
Author: Philipp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-03 00:40:01

Nie mogę uwierzyć, że nikt nie połączył się z tym .

Zasadniczo artykuł argumentuje za "zasadą zera". Nie wypada mi cytować całego artykułu, ale uważam, że jest to główny punkt:

Klasy, które mają własne destruktory, konstruktory kopiowania / przenoszenia lub operatory przypisywania kopiowania / przenoszenia powinny zajmować się wyłącznie własnością. Inne klasy nie powinny mieć własnych destruktorów, Kopiuj/przenoś konstruktory lub operatory przypisania kopiowania/przenoszenia.

Także ten bit jest IMHO ważny:

Wspólne klasy "ownership-in-a-package" są zawarte w standardzie biblioteka: std::unique_ptr i std::shared_ptr. Poprzez zastosowanie niestandardowe obiekty deleter, oba zostały wystarczająco elastyczne, aby zarządzać praktycznie każdego rodzaju zasobów.

 61
Author: NoSenseEtAl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-04 03:08:31

Nie sądzę, reguła trzech jest regułą, która mówi, że klasa, która implementuje jedną z poniższych, ale nie wszystkie, jest prawdopodobnie błędna.

  1. Konstruktor kopiujący
  2. operator przypisania
  3. Destructor

Jednak pominięcie konstruktora move lub operatora przypisania move nie oznacza błędu. To Może być straconą szansą na optymalizację (w większości przypadków) lub że semantyka move nie jest istotna dla tej klasy, ale to nie jest pluskwa.

Chociaż najlepszym rozwiązaniem może być zdefiniowanie konstruktora ruchu, gdy jest to istotne, nie jest to obowiązkowe. std::complex) i wszystkie klasy, które zachowują się poprawnie w C++03, będą zachowywać się poprawnie w C++0x, nawet jeśli nie zdefiniują konstruktora ruchu.

 18
Author: Motti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-24 14:56:36

Tak, myślę, że byłoby miło dostarczyć konstruktor move dla takich klas, ale pamiętaj, że:

  • To tylko optymalizacja.

    Zaimplementowanie tylko jednego lub dwóch konstruktorów kopiujących, operatora przypisania lub destruktora prawdopodobnie doprowadzi do błędów, podczas gdy brak konstruktora move potencjalnie zmniejszy wydajność.

  • Konstruktor Move nie zawsze może być zastosowany bez modyfikacji.

    Niektóre klasy zawsze mają swoje wskaźniki przydzielone, a stąd takie klasy zawsze kasują swoje wskaźniki w destruktorze. W takich przypadkach musisz dodać dodatkowe kontrole, aby stwierdzić, czy ich wskaźniki są przydzielone lub zostały przeniesione (są teraz null).

 14
Author: peoro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-01 13:29:46

Oto krótka aktualizacja na temat obecnego stanu i związanych z nim wydarzeń od 24 stycznia '11.

Zgodnie ze standardem C++11 (patrz załącznik D [depr.impldec]):

Domyślna deklaracja konstruktora kopiującego jest przestarzała, jeśli klasa ma zadeklarowany przez użytkownika operator przypisania kopii lub zadeklarowany przez użytkownika Destruktor. Domyślna deklaracja operatora przypisania kopii jest przestarzała, jeśli klasa ma zadeklarowany przez użytkownika Konstruktor kopiujący lub zadeklarowany przez użytkownika Destruktor.

W rzeczywistości zaproponowano przestarzałe przestarzałe zachowanie nadające C++14 prawdziwą "regułę pięciu" zamiast tradycyjnej "reguły trzech". W 2013 roku EWG głosowała przeciwko tej propozycji, która ma zostać wdrożona w C++2014. Główne uzasadnienie decyzji w sprawie wniosku miało związek z ogólnymi obawami dotyczącymi złamania istniejącego Kodeksu.

Ostatnio zaproponowano ponowne dostosowanie sformułowania C++11 tak, aby osiągnąć nieformalny Reguła pięciu, mianowicie że

Kompilator nie może generować funkcji kopiowania, przesuwania ani destruktora, jeśli którakolwiek z tych funkcji jest dostarczana przez użytkownika.

Jeśli zostanie zatwierdzona przez EWG," reguła " prawdopodobnie zostanie przyjęta dla C++17.

 8
Author: Andrey Rekalo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-11 19:17:23

Zasadniczo wygląda to tak: jeśli nie zadeklarujesz żadnych operacji przeniesienia, powinieneś przestrzegać zasady trzech. Jeśli zadeklarujesz operację move, nie ma nic złego w "naruszeniu" zasady trzech, ponieważ generowanie operacji generowanych przez kompilator stało się bardzo restrykcyjne. Nawet jeśli nie zadeklarujesz operacji przeniesienia i nie złamiesz zasady three, kompilator C++0x powinien dać Ci ostrzeżenie w przypadku, gdy jedna specjalna funkcja została zadeklarowana przez użytkownika, a inne specjalne funkcje zostały automatyczne generowanie ze względu na przestarzałą "regułę zgodności C++03".

Myślę, że można śmiało powiedzieć, że ta zasada staje się trochę mniej znacząca. Prawdziwy problem w C++03 polega na tym, że implementacja różnych semantyki kopii wymaga deklaracji wszystkich powiązanych funkcji specjalnych, tak aby żadna z nich nie była generowana przez kompilator (co w przeciwnym razie zrobiłoby coś złego). Ale C++0x zmienia zasady dotyczące generowania funkcji specjalnych. Jeśli użytkownik zadeklaruje tylko jedną z tych funkcje zmiana semantyki kopiowania uniemożliwi kompilatorowi automatyczne generowanie pozostałych funkcji specjalnych. Jest to dobre, ponieważ brakująca deklaracja zamienia błąd runtime w błąd kompilacji (lub przynajmniej Ostrzeżenie). Jako miara zgodności C++03 niektóre operacje są nadal generowane, ale ta generacja jest uważana za przestarzałą i powinna przynajmniej generować ostrzeżenie w trybie C++0x.

Ze względu na dość restrykcyjne zasady dotyczące generowanych przez kompilator funkcji specjalnych i zgodność Z C++03, reguła trzech pozostaje regułą trzech.

Oto kilka exapli, które powinny pasować do najnowszych reguł C++0x:

template<class T>
class unique_ptr
{
   T* ptr;
public:
   explicit unique_ptr(T* p=0) : ptr(p) {}
   ~unique_ptr();
   unique_ptr(unique_ptr&&);
   unique_ptr& operator=(unique_ptr&&);
};

W powyższym przykładzie nie ma potrzeby deklarowania innych funkcji specjalnych jako usuniętych. Po prostu nie będą generowane ze względu na restrykcyjne zasady. Obecność zadeklarowanych przez użytkownika operacji przeniesienia wyłącza generowane przez kompilator operacje kopiowania. Ale w takim przypadku:

template<class T>
class scoped_ptr
{
   T* ptr;
public:
   explicit scoped_ptr(T* p=0) : ptr(p) {}
   ~scoped_ptr();
};

Kompilator C++0x ma teraz generuje ostrzeżenie o możliwych operacjach kopiowania generowanych przez kompilator, które mogą zrobić złą rzecz. Tutaj zasada trzech spraw i powinny być przestrzegane. Ostrzeżenie w tym przypadku jest całkowicie odpowiednie i daje użytkownikowi szansę na poradzenie sobie z błędem. Możemy pozbyć się problemu za pomocą usuniętych funkcji:

template<class T>
class scoped_ptr
{
   T* ptr;
public:
   explicit scoped_ptr(T* p=0) : ptr(p) {}
   ~scoped_ptr();
   scoped_ptr(scoped_ptr const&) = delete;
   scoped_ptr& operator=(scoped_ptr const&) = delete;
};

Zasada trzech nadal obowiązuje tutaj tylko ze względu na kompatybilność z C++03.

 4
Author: sellibitze,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-24 20:14:41

Nie możemy powiedzieć, że reguła 3 staje się regułą 4 (lub 5) bez łamania całego istniejącego kodu, który wymusza regułę 3 i nie implementuje żadnej formy semantyki ruchu.

Zasada 3 oznacza, że jeśli zaimplementujesz jedną, musisz zaimplementować wszystkie 3.

Również nie są świadomi, że pojawi się jakiś automatycznie wygenerowany ruch. Celem" reguły 3 " jest to, że automatycznie istnieją i jeśli zaimplementujesz jedną, najprawdopodobniej Domyślna implementacja dwóch pozostałych jest błędna.

 3
Author: CashCow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-24 14:07:37

W ogólnym przypadku, to tak, zasada trzech stała się po prostu piątką, z operatorem przypisania ruchu i konstruktorem ruchu dodanym. Jednak nie wszystkie klasy są kopiowalne i ruchome, niektóre są po prostu ruchome, niektóre są po prostu kopiowalne.

 2
Author: Puppy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-24 14:16:09