2016-04-28 11 views
9

Diciamo che abbiamo seguente gerarchia:Come prevenire chiamata alla implementazione base di un metodo

class Abstract 
{ 
public: 
    virtual void foo() = 0; 
}; 

class Base : public Abstract 
{ 
public: 
    virtual void foo() override; //provides base implementation 
}; 

class Derived : public Base 
{ 
public: 
    virtual void foo() override; //provides derived implementation 
}; 

Se Base::foo() viene mai chiamato sull'oggetto Derived che oggetto si desincronizzazione ed i suoi dati saranno danneggiati. Assorbe la struttura dei dati di Base e la sua manipolazione, ma deve eseguire operazioni aggiuntive, quindi chiamare solo il ometterà queste operazioni extra e, di conseguenza, lo stato di Derived sarà danneggiato.

Perciò vorrei evitare che la chiamata diretta di Base realizzazione di foo quindi questo:

Derived d; 
d.Base::foo(); 

idealmente, dovrebbe darmi un errore di tempo di compilazione di alcune specie. O non fare nulla o altrimenti essere prevenuto.

Tuttavia potrebbe essere che sto violando le regole polimorfismo e dovrei usare la composizione, invece, ma che richiederebbe un sacco di battitura a macchina in più ...

+0

Qualcosa non va con 'protected:' piuttosto che 'public:' per quel metodo in 'Base'?Inoltre, questo sembra anche un candidato decente per ridichiarare come puro-virtuale in 'Base' * e * fornendo un'implementazione' Base', che non è comune, ma che accade. Se 'Derived' dovrebbe * sempre * essere implementato, sembrerebbe una scelta accettabile. – WhozCraig

+0

@WhozCraig Non sapevo di poter fornire l'implementazione per il metodo virtuale puro. Questo impedisce che quell'implementazione venga mai chiamata se è stata reimplementata da coloro che la ereditano? – Resurrection

+1

@Resurrection no, è per questo che 'protected' su' Base :: foo' arriverà. Ma ciò che * fa * fa è forzare le classi derivate a fornire ancora override, fornendo anche un'implementazione base comune che possono invocare senza sballottare una funzione membro aggiuntiva nel malessere. – WhozCraig

risposta

22

Come su template method pattern:

class Abstract 
{ 
public: 
    void foo() { foo_impl(); } 
private: 
    virtual void foo_impl() = 0; 
}; 

class Base : public Abstract 
{ 
private: 
    virtual void foo_impl() override; //provides base implementation 
}; 

class Derived : public Base 
{ 
private: 
    virtual void foo_impl() override; //provides derived implementation 
}; 

poi

void test(Abstract& obj) { 
    obj.foo(); // the correct foo_impl() will be invoked 
} 
Derived d; 
test(d); // impossible to call the foo_impl() of Base 
+8

In generale, è una buona idea nascondere il polimorfismo dietro le funzioni del modello non polimorfico, non solo in questo Astuccio. – leemes

8

È possibile effettuare tutte le foo() metodi non public, quindi hanno un non - Funzione virtual nella classe Abstract che chiama semplicemente foo.

+1

Per "può" leggere "probabilmente dovrebbe" (come regola generale - anche quando non si sta tentando di impedire che le persone si sparino al piede). –

+1

@ Martin Bonner - vero, obbligato. collegamento [Interfaccia non virtuale] (https://en.wikipedia.org/wiki/Non-virtual_interface_pattern) ecc .... –

12

è possibile esplorare la template method pattern. Consente un maggiore controllo dell'esecuzione dei metodi coinvolti.

class Abstract 
{ 
public: 
    virtual void foo() = 0; 
}; 

class Base : public Abstract 
{ 
protected: 
    virtual void foo_impl() = 0; 
public: 
    //provides base implementation and calls foo_impl() 
    virtual void foo() final override { /*...*/ foo_impl(); } 
}; 

class Derived : public Base 
{ 
protected: 
    virtual void foo_impl() override; //provides derived implementation 
}; 

Il modello è visto nella biblioteca iostreams con sync() e pubsync() metodi.

Per impedire le chiamate dirette e mantenere lo stato coerente, è necessario ottenere l'implementazione final del metodo foo nella posizione corretta nello stack. Se l'intenzione è quella di vietare la chiamata diretta dalla parte superiore della gerarchia, è possibile spostare i metodi _impl.

Vedere anche l'interfaccia non virtuale, the NVI pattern.


Tenete a mente pure che i metodi imperative non devono avere lo stesso specificatore di accesso della classe Abstract. Si potrebbero anche solo eseguire i metodi nelle classi derivate private o protected;

class Abstract 
{ 
public: 
    virtual void foo() = 0; 
}; 

class Base : public Abstract 
{ 
    virtual void foo() override; //provides base implementation 
}; 

class Derived : public Base 
{ 
    virtual void foo() override; //provides derived implementation 
}; 

Nota: se non diversamente previsto, cambiando la specificatore di accesso potrebbe essere considerato cattivo design - in modo sostanzialmente se si fa cambiare lo specificatore di accesso, ci dovrebbe dovrebbe essere un buon motivo per farlo.

+0

Grazie per il suggerimento! Ho usato questo modello prima, ma per qualche motivo non mi è venuto in mente che avrebbe risolto il mio problema attuale. Ad ogni modo, penso che il foo virtuale sia eccessivo in questo caso e il foo non virtuale in Abstract con foo_impl virtuale sarebbe in realtà sufficiente come suggerito dagli altri. Ma sì, questo è per quando vorrei consentire un controllo veramente completo. – Resurrection

+0

Certo, quale livello si aggancia al modello di metodo del modello dipende da cos'altro si vuole fare. L'ho messo lì da quando hai menzionato quel punto nella domanda. Anche il mantenimento di una classe con sole funzioni virtuali pure può essere vantaggioso (minimizza la dipendenza dal linker ad esempio oltre un limite di dll). – Niall

+0

Punto interessante. Lo prenderò in considerazione quando lo riscriverò. Per quanto riguarda la modifica: ciò è certamente possibile (anche se ho letto altrove è un cattivo design per ridigitare identificatori del genere) ma non in questo caso perché Base deve funzionare come previsto da Abstract quindi non posso nascondere l'interfaccia pubblica o parti di essa in questo Astuccio. – Resurrection

Problemi correlati