2014-09-25 8 views
10

Supponiamo che abbiamo una base class A:La regola dei tre/cinque si applica all'ereditarietà e ai distruttori virtuali?

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; 
}; 

Vogliamo estendere questa classe con class B in modo da aggiungiamo il nostro distruttore virtuale, modificare un membro per virtual e cambiare private-protected per 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()); 
     } 
}; 

Ora, poiché abbiamo aggiunto un distruttore in class A abbiamo bisogno di creare un costruttore di copia e copia operatore in questo modo?

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; 

}; 

A me questo sembra inutile come l'operatore di copia di default e costruttore di copia avrebbe fatto la stessa cosa.

+2

@MarcoA. Il distruttore di una classe derivata potrebbe dover fare qualcosa in più e il distruttore della classe base deve essere virtuale se si desidera eliminare un derivato da un puntatore alla base. –

+0

@Angew questa è una buona ragione. Right –

+0

Nota: evitare di utilizzare i membri di dati 'protected' in generale; perché, a meno che non controlli strettamente la gerarchia dell'ereditarietà ('finale'), hai perso tutte le garanzie sullo stato di questo membro in generale. Questo è simile alla restituzione di un riferimento non const. –

risposta

15

Per essere preparati alla possibile evoluzione futura del linguaggio, è necessario impostare esplicitamente i costruttori di copia/spostamento e gli operatori di assegnazione quando si aggiunge un distruttore virtuale. Questo perché C++ 11, 12.8/7 rende deprecata la generazione implicita di costruttori di copia quando la classe ha un distruttore dichiarato dall'utente.

Fortunatamente, C++ 11 di inadempienza esplicito rende la loro definizione semplice:

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

Mi piace questa risposta, suona come una buona pratica "specifica/paga per quello che usi" –

+3

FWIW, puoi anche '= default' il distruttore virtuale. –

+0

Un distruttore 'virtuale ~ A() = default' continuerà a contare come utente generato? – MatthiasB

4

Se il distruttore non fa nulla, poi c'è (di solito) non è necessario per le operazioni di copia/spostamento a fare qualcosa diverso da quello predefinito. Non c'è certamente bisogno di scrivere versioni che facciano le impostazioni predefinite, solo per soddisfare un'eccessiva semplificazione della regola. Tutto ciò che fa è aumentare la complessità del codice e la possibilità di errore.

Tuttavia, se si dichiara un distruttore virtuale, che indica che la classe è destinata a essere una classe di base polimorfica, è possibile prendere in considerazione l'eliminazione delle operazioni di copia/spostamento per impedire l'affettatura.

This article dà una formulazione utile per la regola, tra cui

Se una classe ha un distruttore non vuota, ha bisogno quasi sempre un costruttore di copia e un operatore di assegnazione.

+0

alcune classi semplicemente non hanno bisogno di funzioni membro speciali - dipende da cosa è necessario –

8

La regola del tre si applica a tutto.

Se la classe è destinata a essere utilizzata come base polimorfica, è altamente improbabile che si desideri utilizzare il suo costruttore di copie perché taglia. Quindi devi prendere una decisione. Questo è il significato della regola dei tre: non puoi scegliere di avere un distruttore senza considerare i membri speciali della copia.

Si noti che la regola del tre non dice che si suppone di implementare il costruttore di copia e copiare operatore di assegnazione. In teoria, dovresti gestirli in qualche modo, perché è molto probabile che lo standard generato non sia adatto se hai il tuo distruttore (fette!), Ma il modo in cui li gestisci non deve essere implementato.

Probabilmente si dovrebbe semplicemente vietarlo in quanto utilizzando basi polimorfici e semantica di valore tendono a mescolare come l'acqua e l'olio.

Credo che si potrebbe forse rendere protetto classi in modo derivate può chiamare per le proprie copie, anche se mi considero ancora che una scelta discutibile.

Inoltre, poiché C++ 11 la generazione di membri speciali di copia è deprecata quando un distruttore è dichiarato dall'utente. Ciò significa che se si desidera che il codice sia compatibile in avanti, anche se si desidera che il comportamento di costruttore di copia di default (una scelta discutibile), si vuole fare quella esplicita. È possibile utilizzare = default per quello.

+0

Questo è migliore della risposta accettata. Nessuna risposta qui è completa senza menzionare l'affettatura. Inoltre, "le basi polimorfiche e la semantica del valore tendono a mescolarsi come l'acqua e l'olio" è un'osservazione molto buona e merita di essere presa in considerazione se (come me) state cercando questo Q & A. Suppongo che la roba di cancellazione del tipo possa creare un'eccezione, ma questo è lontano dal caso comune. – Nemo

Problemi correlati