Perché i costruttori non sono ereditati?

26

Sono confuso su quali potrebbero essere i problemi se un costruttore è stato ereditato da una classe base. Cpp Primer Plus dice,

Constructors are different from other class methods in that they create new objects, whereas other methods are invoked by existing objects. This is one reason constructors aren’t inherited. Inheritance means a derived object can use a base-class method, but, in the case of constructors, the object doesn’t exist until after the constructor has done its work.

Capisco che il costruttore sia chiamato prima che la costruzione dell'oggetto sia completata.

Come può portare a problemi se una classe figlio eredita ( Con l'ereditazione intendo che la classe figlio è in grado di sovrascrivere il metodo della classe genitore ecc. Non solo accedendo al metodo della classe genitore ) il costruttore genitore ?

Capisco che non vi è alcuna necessità di chiamare esplicitamente un costruttore da un codice [non che io sappia già.] tranne durante la creazione di oggetti. Anche in questo caso, è possibile utilizzare tale meccanismo per richiamare il costitutore principale [In cpp, utilizzando :: o utilizzando member initialiser list , in java utilizzando super ]. In Java c'è un'attuazione per chiamarla nella prima riga, capisco che è quello di garantire che l'oggetto padre sia creato prima e poi i proventi della costruzione dell'oggetto figlio.

Può sovrascrivere . Ma non riesco a trovare situazioni in cui ciò possa rappresentare un problema. Se il figlio eredita il costruttore genitore cosa può andare storto?

Quindi è giusto per evitare di ereditare funzioni non necessarie. O c'è dell'altro?

    
posta Suvarna Pattayil 13.05.2013 - 10:48
fonte

7 risposte

34

Non può esserci alcuna corretta ereditarietà dei costruttori in C ++, perché il costruttore di una classe derivata deve eseguire azioni aggiuntive che un costruttore della classe base non deve fare e non sa. Queste azioni aggiuntive sono l'inizializzazione dei membri di dati della classe derivata (e in un'implementazione tipica, anche impostando il vpointer in riferimento alle classi derivate vtable).

Quando una classe viene costruita, ci sono un certo numero di cose che devono sempre accadere: i costruttori della classe base (se ce ne sono) e i membri diretti devono essere chiamati e se ci sono delle funzioni virtuali, il vpointer deve essere impostato correttamente. Se non si fornisce un costruttore per la classe, il compilatore lo creerà uno che esegue le azioni richieste e nient'altro. Se fai fornisce un costruttore, ma perdi alcune delle azioni richieste (ad esempio, l'inizializzazione di alcuni membri), il compilatore aggiungerà automaticamente le azioni mancanti al tuo costruttore. In questo modo, il compilatore assicura che ogni classe abbia almeno un costruttore e che ogni costruttore inizializzi completamente gli oggetti che crea.

In C ++ 11, è stata introdotta una forma di "ereditarietà del costruttore" in cui puoi istruire il compilatore a generare per te un insieme di costruttori che prendono gli stessi argomenti dei costruttori della classe base e che li inoltrano semplicemente argomenti alla classe base.
Sebbene sia ufficialmente chiamato ereditarietà, non è vero perché esiste ancora una funzione specifica di classe derivata. Ora viene generato dal compilatore invece di essere scritto esplicitamente da te.

Questa funzione funziona in questo modo:

struct Base {
    Base(int a) : i(a) {}
    int i;
};

struct Derived : Base {
    Derived(int a, std::string s) : Base(a), m(s) {}

    using Base::Base; // Inherit Base's constructors.
    // Equivalent to:
    //Derived(int a) : Base(a), m() {}

    std::string m;
};

Derived ora ha due costruttori (non contando i costruttori copia / sposta). Uno che prende un int e una stringa e uno che prende solo un int.

    
risposta data 13.05.2013 - 11:48
fonte
5

Non è chiaro cosa intendi per "ereditare il costruttore genitore". Usa la parola overriding , il che suggerisce che potresti pensare a costruttori che si comportano come funzioni virtuali polimorfiche . Non uso deliberatamente il termine "costruttori virtuali" perché è un nome comune per un modello di codice dove hai effettivamente bisogno di un'istanza già esistente di un oggetto per crearne un'altra.

C'è poca utilità per i costruttori polimorfici al di fuori del modello del "costruttore virtuale" ed è difficile trovare uno scenario concreto in cui un costruttore polimorfico possa essere utilizzato. Un esempio molto forzato che non è in alcun modo nemmeno C ++ remotamente valido:

struct Base {
  virtual Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) override {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Base(p1, p2); // This might call Derived(unsigned, unsigned).
  Derived *p_d2 = nullptr;
  p_d2 = new Base(p1, p2); // This might call Derived(unsigned, unsigned) too.
}

In questo caso il costruttore chiamato dipende dal tipo concreto della variabile costruita o assegnata. È complesso da rilevare durante l'analisi / generazione del codice e non ha utilità: si conosce il tipo concreto che si sta costruendo e si è scritto un costruttore specifico per la classe derivata. Il seguente codice valido C ++ fa esattamente lo stesso, è leggermente più corto ed è più esplicito in quello che fa:

struct Base {
  Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Derived(p1, p2); 
  Derived *p_d2 = nullptr;
  p_d2 = new Derived(p1, p2);
}

Una seconda interpretazione o forse una domanda aggiuntiva è - cosa succederebbe se i costruttori della classe Base fossero automaticamente presenti in tutte le classi derivate a meno che non fossero esplicitamente nascosti.

If the child does inherit the parent constructor what can go wrong?

Dovresti scrivere un codice aggiuntivo per nascondere un costruttore genitore che non è corretto da utilizzare nella costruzione della classe derivata. Questo può accadere quando la classe derivata specializza la classe base in modo tale che alcuni parametri diventino irrilevanti.

L'esempio tipico è rettangoli e quadrati (nota che i quadrati e i rettangoli in genere non sono sostituibili con Liskov, quindi non è un ottimo progetto, ma evidenzia il problema).

struct Rectangle {
  Rectangle(unsigned width, unsigned height) {...}
};

struct Square : public Rectangle {
  explicit Square(unsigned side) : Rectangle(side, side) {...}
};

Se Square ha ereditato il costruttore a due valori di Rectangle, potresti costruire quadrati con un'altezza e una larghezza diverse ... Questo è logicamente sbagliato, quindi vorresti nascondere quel costruttore.

    
risposta data 13.05.2013 - 12:16
fonte
3

Il problema più ovvio nel consentire alla classe derivata di sovrascrivere il costruttore della classe base è che lo sviluppatore della classe derivata è ora responsabile di sapere come costruire le sue classi di base. Cosa succede quando la classe derivata non costruisce correttamente la classe base?

Inoltre, il principio di sostituzione di Liskov non si applica più poiché non puoi più contare sulla tua collezione di oggetti di classe base per essere compatibili tra loro, perché non c'è alcuna garanzia che la classe base sia stata costruita correttamente o compatibilmente con l'altra tipi derivati

È ulteriormente complicato quando si aggiunge più di 1 livello di ereditarietà. Ora la tua classe derivata deve sapere come costruire tutte le classi base lungo la catena.

Quindi cosa succede se aggiungi una nuova classe base all'inizio della gerarchia dell'ereditarietà? Dovresti aggiornare tutti i costruttori di classi derivate.

    
risposta data 14.05.2013 - 16:01
fonte
2

I costruttori sono fondamentalmente diversi dagli altri metodi:

  1. Vengono generati se non li scrivi.
  2. Tutti i costruttori della classe base sono chiamati implicitamente anche se non lo fai manualmente
  3. Non li chiami esplicitamente ma creando oggetti.

Quindi perché non sono ereditati? Risposta semplice: perché c'è sempre un override, generato o scritto manualmente.

Perché ogni classe ha bisogno di un costruttore? Questa è una domanda complicata e credo che la risposta dipenda dal compilatore. Esiste una cosa come un costruttore "banale" per il quale il compilatore non impone che venga chiamato come sembra. Penso che sia la cosa più vicina a ciò che intendi per ereditarietà, ma per le tre ragioni sopra esposte penso che confrontare i costruttori con i metodi normali non sia davvero utile. :)

    
risposta data 13.05.2013 - 15:41
fonte
2

Perché i costruttori non sono ereditati: la risposta è sorprendentemente semplice: Il costruttore della classe base "crea" la classe base e il costruttore della classe ereditata "crea" la classe ereditata. Se la classe ereditata ereditasse il costrutto, il costruttore tenterebbe di creare un oggetto di tipo base class e non sarebbe possibile "creare" un oggetto di tipo classe ereditata.

Che tipo di sconfigge lo scopo di ereditare una classe.

    
risposta data 14.05.2013 - 11:49
fonte
0

Puoi usare:

MyClass() : Base()

Stai chiedendo a perché devi farlo?

La sottoclasse potrebbe avere proprietà aggiuntive che potrebbero dover essere inizializzate nel costruttore, oppure potrebbe inizializzare le variabili della classe base in un modo diverso.

In quale altro modo potresti creare l'oggetto sottotipo?

    
risposta data 13.05.2013 - 11:47
fonte
0

Ogni classe ha bisogno di un costruttore, anche di quelli di default.
C ++ creerà per te costruttori predefiniti tranne se crei un costruttore specializzato.
Nel caso in cui la tua classe base usi un costruttore specializzato, dovrai scrivere il costruttore specializzato sulla classe derivata anche se entrambi sono uguali e riconducili indietro.
C ++ 11 ti consente di evitare la duplicazione del codice sui costruttori usando utilizzando :

Class A : public B {
using B:B;
....
  1. Un insieme di costruttori ereditari è composto da

    • Tutti i costruttori non di modello della classe base (dopo aver omesso i parametri di ellissi, se presenti) (dal C ++ 14)
    • Per ogni costruttore con argomenti predefiniti o con i puntini di sospensione, tutte le firme del costruttore formate eliminando i puntini di sospensione e omettendo gli argomenti predefiniti dalle estremità dell'argomento vengono elencate una alla volta
    • Tutti i modelli di costruttori della classe base (dopo aver omesso i parametri di ellissi, se presenti) (dal C ++ 14)
    • Per ogni modello di costruttore con argomenti predefiniti o puntini di sospensione, tutte le firme del costruttore formate eliminando i puntini di sospensione e omettendo gli argomenti predefiniti dalla fine dell'argomento vengono elencati uno alla volta
  2. Tutti i costruttori ereditati che non sono il costruttore predefinito o il costruttore di copia / spostamento e le cui firme non corrispondono ai costruttori definiti dall'utente nella classe derivata, sono implicitamente dichiarati nella classe derivata. I parametri predefiniti non sono ereditati

risposta data 17.01.2016 - 19:50
fonte

Leggi altre domande sui tag