2014-09-25 6 views
10

Załóżmy mamy bardzo prosty class A:Czy zasada 3/5 ma zastosowanie do dziedziczenia i wirtualnych destruktorów?

class A { 
    public: 
     void SetName(const std::string& newName) { 
      m_name=newName; 
     } 

     void Print() const { 
      std::printf("A::Print(). Name: %s\n",m_name.c_str()); 
     } 
    private: 
     std::string m_name; 
}; 

Chcemy rozszerzyć tę klasę z class B więc dodamy nasz wirtualny destruktor, zmień członka do virtual i zmienić private do protected do inh:

class A { 
    public: 
     virtual ~A() {} 

     void SetName(const std::string& newName) { 
      m_name=newName; 
     } 

     virtual void Print() const { 
      std::printf("A::Print(). Name: %s\n",m_name.c_str()); 
     } 
    protected: 
     std::string m_name; 

}; 

class B : public A { 
    public: 
     virtual void Print() const { 
      std::printf("B::Print(). Name: %s\n",m_name.c_str()); 
     } 
}; 

Odkąd dodaliśmy destruktor w class A, musimy stworzyć konstruktora kopiowania i skopiować operację tak jak?

class A { 
    public: 
     virtual ~A() {} 

     A() = default; 
     A(const A& copyFrom){ 
      *this = copyFrom; 
     } 
     virtual A& operator=(const A& copyFrom){ 
      m_name=copyFrom.m_name; 
      return *this; 
     }; 

     void SetName(const std::string& newName) { 
      m_name=newName; 
     } 

     virtual void Print() const { 
      std::printf("A::Print(). Name: %s\n",m_name.c_str()); 
     } 
    protected: 
     std::string m_name; 

}; 

Dla mnie wydaje się to zbędne, ponieważ domyślny operator kopiowania i konstruktor kopii robią to samo.

+2

@MarcoA. Destrukcja klasy pochodnej może wymagać zrobienia czegoś ekstra, a destruktor klasy podstawowej musi być wirtualny, jeśli chcesz usunąć wyprowadzone ze wskaźnika do bazy. –

+0

@Angew to dobry powód. Prawo –

+0

Uwaga: należy unikać używania ogólnie "chronionych" elementów danych; ponieważ jeśli nie ściśle kontrolujesz hierarchię dziedziczenia ('final'), straciłeś wszystkie gwarancje dotyczące ogólnego stanu tego elementu. Jest to podobne do zwracania odniesienia niestałego. –

Odpowiedz

15

Aby być przygotowanym na potencjalną ewolucję języka w przyszłości, powinieneś jawnie domyślić się konstruktorów kopiowania/przenoszenia i operatorów przydziału, gdy dodasz wirtualny destruktor. Dzieje się tak, ponieważ C++ 11, 12.8/7 sprawia, że ​​niejawne generowanie konstruktorów kopiowania jest przestarzałe, gdy klasa ma destruktor deklarowany przez użytkownika.

szczęście C++ 11 za jawne zalegających czyni ich definicji łatwe:

class A { 
    public: 
     virtual ~A() {} 

     A() = default; 
     A(const A& copyFrom) = default; 
     A& operator=(const A& copyFrom) = default; 
     A(A &&) = default; 
     A& operator=(A &&) = default; 

     void SetName(const std::string& newName) { 
      m_name=newName; 
     } 

     virtual void Print() const { 
      std::printf("A::Print(). Name: %s\n",m_name.c_str()); 
     } 
    protected: 
     std::string m_name; 

}; 
+0

Podoba mi się ta odpowiedź, brzmi jak dobra "określ/zapłać za to, czego używasz" praktyka –

+3

FWIW, możesz również '= default' wirtualnego destruktora. –

+0

Czy "wirtualny ~ A() = domyślny" destruktor nadal będzie liczony jako wygenerowany przez użytkownika? – MatthiasB

4

Jeśli destruktor niczego nie robi, wówczas (zwykle) nie ma potrzeby, aby operacje kopiowania/przenoszenia wykonywały inne czynności niż domyślne. Z pewnością nie ma potrzeby pisania wersji, które robią to, co domyślne, tylko po to, by zasymulować nadmierne uproszczenie reguły. Wszystko to zwiększa złożoność kodu i zakres błędów.

Jeśli jednak zadeklarujesz wirtualny destruktor, wskazując, że klasa ma być polimorficzną klasą bazową, możesz rozważyć usunięcie operacji kopiowania/przenoszenia, aby zapobiec rozcięciu.

This article daje przydatnych sformułowań dla rządów, w tym

Jeśli klasa ma niepusty destruktor, to prawie zawsze potrzebuje konstruktor kopiujący i operator przypisania.

+0

niektóre klasy nie wymagają specjalnych funkcji - zależą od tego, co jest potrzebne. –

8

Zasada trzech odnosi się do wszystkiego.

Jeśli twoja klasa ma być używana jako baza polimorficzna, jest bardzo mało prawdopodobne, że będziesz chciał użyć jej konstruktora kopiowania, ponieważ jest ona wycinana. Musisz podjąć decyzję. Na tym polega zasada trójki: nie można wybrać destruktora bez uwzględnienia specjalnych członków kopii.

Należy pamiętać, że reguła trzech nie mówi, że należy wprowadzić konstruktora kopiowania i operatora przypisania kopiowania. Powinniście jakoś sobie z nimi poradzić, ponieważ wygenerowany domyślnie jest wysoce prawdopodobny, że nie nadaje się, jeśli macie własny destruktor (to plasterki!), Ale sposób, w jaki sobie z nimi radzicie, nie musi ich realizować.

Powinieneś po prostu zabronić tego, ponieważ używasz polimorficznych zasad, a semantyka wartości miesza się jak woda i olej.

Sądzę, że mógłbyś uczynić go chronionym, więc klasy pochodne mogą nazywać je własnymi kopiami, choć nadal uważam, że jest to wątpliwy wybór.

Dodatkowo, ponieważ C++ 11 generowanie kopii specjalnych elementów jest przestarzałe, gdy destruktor jest deklarowany przez użytkownika. Oznacza to, że jeśli chcesz, aby twój kod był kompatybilny z przyszłością, nawet jeśli chcesz zachować domyślne zachowanie konstruktora kopii (wątpliwy wybór), będziesz chciał to wyraźnie wyrazić. Możesz użyć do tego celu = default.

+0

To jest lepsze niż zaakceptowana odpowiedź. Żadna odpowiedź tutaj nie jest kompletna bez wspominania o krojeniu. Również "polimorficzne zasady i semantyka wartości mieszają się jak woda i olej" to bardzo dobra obserwacja i warte rozważenia, czy (jak ja) szukasz tego pytania i odpowiedzi. Przypuszczam, że głupie rzeczy do usuwania typu mogą stworzyć wyjątek, ale to daleko od powszechnego przypadku. – Nemo

Powiązane problemy