Forza tutte le classi a implementare/sostituire un metodo "virtuale puro" nella gerarchia di ereditarietà multilivello

StackOverflow https://stackoverflow.com/questions/9477581

Domanda

In C++ perché il puro virtual metodo impone la sua sostituzione obbligatoria soltanto ai suoi figli immediati (per la creazione di oggetti), ma non ai nipoti e così via?

struct B {
  virtual void foo () = 0;
};
struct D : B {
  virtual void foo () { ... };
};

struct DD : D {
  // ok! ... if 'B::foo' is not overridden; it will use 'D::foo' implicitly
};

Non vedo un grosso problema nel lasciare fuori questa funzione.
Ad esempio, dal punto di vista della progettazione del linguaggio, sarebbe stato possibile che, struct DD è consentito utilizzare D::foo solo se ha qualche dichiarazione esplicita come using D::foo;.Altrimenti deve prevalere foo obbligatorio.

Esiste un modo pratico per ottenere questo effetto in C++?

È stato utile?

Soluzione

Ho trovato un meccanismo in cui almeno ci viene richiesto di annunciare il metodo sovrascritto esplicitamente.Non è il modo perfetto, ma almeno un po' vicino.

Supponiamo che ne abbiamo pochi puri virtual metodi dentro class B (base):

class B {
  virtual void foo () = 0;
  virtual void bar (int) = 0;
};

Tra questi, voglio foo() essere sovrascritto dall'intera gerarchia, quindi astratto foo() 1 livello superiore per semplicità:

class Register_foo {
  template<typename T>  // this matches the signature of 'foo'
  Register_foo (void (T::*)()) {}
};
class Base : public virtual Register_foo {  // <---- virtual inheritance
  virtual void bar (int) = 0;
  Base () : Register_foo(&Base::foo) {}  // <--- explicitly pass the function name
};

Ogni successiva classe figlia nella gerarchia dovrebbe farlo Registrati suo foo al suo interno ogni costruttore esplicitamente.per esempio.:

struct D : B {
  D () : Register_foo(&D::foo) {}
  D (const D &other) : Register_foo(&D::foo) {}
  virtual void foo () {};
};

Questo meccanismo di registrazione non ha nulla a che fare con la logica aziendale.Ma almeno all'interno del costruttore di qualsiasi classe figlia verrebbe menzionato that, which foo sta usando.
Però, il bambino class può scegliere di registrarsi utilizzando il proprio foo o quello del suo genitore foo, ma almeno è così annunciato esplicitamente.

Altri suggerimenti

Che cosa stai fondamentalmente chiedendo è quello di richiedere che il più derivato La classe implementa il functiom.E la mia domanda è: perché?Circa l'unica tempo che posso immaginare che questo per essere rilevante sia una funzione come clone() o another(), che restituisce una nuova istanza dello stesso tipo.E questo è Quello che vuoi davvero far rispettare, che il nuovo istanza ha lo stesso genere;Anche lì, dove la funzione è effettivamente implementata è irrilevante.E puoi far rispettare questo:

class Base
{
    virtual Base* doClone() const = 0;
public:
    Base* clone() const
    {
        Base* results = doClone();
        assert( typeid(*results) == typeid(*this) );
        return results;
    }
}
.

(In pratica, non ho mai trovato persone che dimenticano di ignorare clone a Sii un vero problema, quindi non mi sono mai disturbato con qualcosa come quanto sopra. È una tecnica generalmente utile, tuttavia, ogni volta che vuoi far rispettare Post-condizioni.)

Un virtuale puro significa che da istanziare, il virtuale puro deve essere sovrascritto in alcuni discendente della classe che dichiara la funzione virtuale pura. Ciò può essere nella classe che viene istanziata o qualsiasi classe intermedia tra la base che dichiara il virtuale puro e quello che viene istanziato.

È ancora possibile, tuttavia, avere classi intermedie che derivano da una con un virtuale puro senza sovrascrivere quel puro virtuale. Come la classe che dichiara la pura virtuale, quelle classi possono essere utilizzate solo come classi basate; Non è possibile creare istanze di quelle classi, solo di classi che derivano da loro, in cui è stato implementato ogni virtuale puro.

Per quanto riguarda il fatto che un discendente sovrasta un virtuale, anche se una classe intermedia ha già fatto, la risposta è no, C ++ non fornisce nulla che sia almeno destinato a farlo. Sembra quasi che tu possa essere in grado di tacere qualcosa insieme usando un'eredità multiplo (probabilmente virtuale), quindi l'implementazione nella classe intermedia sarebbe presente, ma il tentativo di usarlo sarebbe ambiguo, ma non ho pensato che sufficiente per essere sicuro Come (o se) funzionerebbe - e anche se lo fece, avrebbe fatto il suo trucco solo quando si tenta di chiamare la funzione in questione, non solo istanziare un oggetto.

Nell'esempio, non hai dichiarato D::foo puro;Ecco perché non ha bisogno di essere sovrascritto.Se vuoi richiedere che sia di nuovo sovrascritto, quindi dichiaralo puro.

Se si desidera essere in grado di istanziare D, ma costringere eventuali ulteriori classi derivate per sovrascrivere foo, allora non puoi.Tuttavia, è possibile ricavare un'altra classe da D che lo ridefronica pure e quindi le classi derivate da che deve sovrascriverlo di nuovo.

.

C'è un modo pratico per avere questo effetto in C ++?

No, e per una buona ragione.Immagina la manutenzione in un grande progetto se questo facesse parte dello standard.Qualche classe base o classe base intermedia deve aggiungere qualche interfaccia pubblica, un'interfaccia astratta.Ora, ogni singolo figlio e nipote, non avrebbero dovuto essere cambiato e ricompilato (anche se fosse semplice come aggiungere con D :: Foo () come hai suggerito), probabilmente vedi dove si tratta di voce, Hells Kitchen.

Se vuoi davvero far rispettare l'implementazione, puoi forzare l'attuazione di qualche altro virtuale puro nella classe Bambino.Questo può anche essere fatto usando anche il modello CRTP.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top