Come impostare la regola c ++ di tre in una classe base virtuale

6

Sto provando a creare una pura classe base virtuale (o simulato puro virtuale)

il mio obiettivo:

  1. L'utente non può creare istanze di BaseClass.
  2. Le classi derivate devono implementare il costruttore predefinito, il costruttore di copia, l'operatore di assegnazione delle copie e il distruttore.

Il mio tentativo:

class Base
{
public:
    virtual ~Base() {};
    /* some pure virtual functions */
private:
    Base() = default;
    Base(const Base& base) = default;
    Base& operator=(const Base& base) = default;
}

Questo dà alcuni errori lamentando che (per uno) il costruttore di copie è privato. Ma non voglio che venga chiamato questo costruttore imitato.
Qualcuno può darmi la costruzione corretta per farlo se questo è possibile?

    
posta Minion91 01.10.2012 - 10:59
fonte

3 risposte

10

Il requisito sembra sbagliato. Il motivo è che i metodi virtuali e i costruttori di copie non lavorano insieme. I metodi virtuali sono utili se si utilizza l'oggetto in modo polimorfico, ovvero tramite riferimento / puntatore a una classe base. Ma il costruttore di copie costruisce sempre l'oggetto del tipo statico in cui è scritto, quindi se gli dai un oggetto polimorfico, solo creerà la classe base, non la sottoclasse che volevi. Questo è chiamato slicing .

Quindi vuoi:

  • Avere un framework basato su modelli che conosce il tipo in fase di compilazione e utilizza il costruttore di copia. Ma questo non ha bisogno di alcun metodo virtuale, né del tipo base comune, dal momento che il codice verrà compilato con il parametro template specifico. Il compilatore si lamenterà se il tipo sostituito non supporta le operazioni che il modello tenta di utilizzare, ma è possibile migliorare leggermente la diagnostica utilizzando alcuni controlli espliciti, ad es. da Controllo Boost.Concept .

  • Avere un framework che utilizza oggetti polimorfici con la classe base, ma che non può creare copie con il costruttore di copie, perché non conosce il tipo da costruire. Ci sono tre vie d'uscita:

    1. Avere un metodo% virtual Base *clone() , che verrà implementato per fare return new Derived(this) in ogni sottoclasse di calcestruzzo. Probabilmente dovresti avvolgerlo immediatamente in unique_ptr o shared_ptr per evitare perdite di copie.
    2. Invece di copie, distribuisci riferimenti o puntatori intelligenti, probabilmente shared_ptr in questo caso. Probabilmente si tratta di riferimenti const / puntatori intelligenti o digitati con interfaccia che contiene solo metodi che non possono influenzare gli invarianti nel framework.
    3. Crea il metodo che inserisce gli oggetti in un modello, che costruisce un cloner e lo memorizza insieme all'oggetto. Cloner è una funzione template<typename T> Base *clone(const Base *obj) { return new T(dynamic_cast<const T &>(*obj); } (puoi avere un functor o una classe con più metodi di supporto come questo). Per assicurarti di avere veramente il tipo corretto, il metodo insert dovrebbe probabilmente racchiudere la chiamata in new così come simile ad es. make_shared .

A proposito, nulla di tutto questo ha nulla a che fare con la regola del tre. Poiché la regola del terzo dice che il costruttore di copie predefinito if , l'operatore di assegnazione predefinito o il distruttore predefinito non sono abbastanza buoni per la classe, rispetto a nessuno di essi. Ma non perché il compilatore lo richiederebbe, ma perché la logica probabilmente fa. Ma per la maggior parte delle classi che vogliono essere copiabili sono abbastanza buone. Poiché nella maggior parte dei casi si nasconde la gestione delle risorse in qualche puntatore intelligente e si lascia semplicemente la chiamata predefinita di copia / assegnazione / distruttore.

    
risposta data 01.10.2012 - 14:59
fonte
2

I am trying to create a pure virtual base class (or simulated pure virtual)

Certo:

User can't create instances of BaseClass.

Facile rendere il distruttore virtuale e puro

class Base
{
    public:
       virtual ~Base() = 0;
};
Base::~Base(){}

Ora non puoi creare un'istanza di un membro di Base.

Derived classes have to implement default constructor, copy constructor, copy assignment operator and destructor.

Non puoi forzare nulla di tutto ciò.
Se l'utente non fornisce nulla di quanto sopra, il compilatore genererà automaticamente versioni pubbliche per te. Ma rendendoli privati hai praticamente reso non copiabile alcuna classe derivata (anche se crei le loro versioni).

Ma in g ++ ci sono alcuni avvertimenti per aiutarti.
Se imposti -Weffc ++ e imposta anche -Werror Dovrebbe generare un errore in fase di compilazione se non si obbedisce alla regola dei tre .

    
risposta data 01.10.2012 - 18:14
fonte
0

Poiché regola dei tre dice ...

If you need to explicitly declare either the destructor, copy constructor or copy assignment operator yourself, you probably need to explicitly declare all three of them.

Nel tuo esempio di classe astratta con la gerarchia di ereditarietà, dovresti rendere autonomo il distruttore, il costruttore di copia e l'operatore di assegnazione delle copie. Non dichiarare nessuno dei due come privato dal momento che limita il loro utilizzo fuori dalla classe.

class Base
{
 /* some pure virtual functions */
 public:
     Base() ;
     Base(const Base& base) ;
     Base& operator=(const Base& base) = default;
     virtual ~Base() {};
} ;

Di fatto, l'operatore di assegnazione non viene trasmesso in ereditarietà.

    
risposta data 01.10.2012 - 11:31
fonte

Leggi altre domande sui tag