2010-10-20 11 views
5

Devo chiamare un metodo virtuale per tutte le classi derivate da una determinata classe di base di base subito dopo la costruzione dell'oggetto derivato. Ma farlo nel costruttore della classe base si tradurrà in un puro metodo virtuale chiamataChiamare il metodo virtuale immediatamente dopo la costruzione

Ecco un esempio semplificato:

struct Loader { 
    int get(int index) { return 0; } 
}; 

struct Base{ 
    Base() { 
     Loader l; 
     load(l); // <-- pure virtual call! 
    } 
    virtual void load(Loader &) = 0; 
}; 

struct Derived: public Base { 
    int value; 
    void load(Loader &l) { 
     value = Loader.get(0); 
    } 
}; 

posso chiamare load presso il costruttore Derived, ma Derived non potevo sapere come per creare un caricatore. Qualche idea/soluzione alternativa?

+0

Qual è il problema? Puoi chiamare un metodo virtuale puro. –

+2

@Benoit: non in un costruttore. @Vargas: Probabilmente può essere progettato meglio, quindi non hai questa dipendenza. Ad esempio, perché 'load' è una funzione separata chiamata nel costruttore? Perché non lasciare che 'Derived' carichi i propri valori. – GManNickG

+0

@Benoit: dal costruttore? !!! Si chiama comportamento non definito in C++ –

risposta

2

Utilizzare il modello Pimpl:

template<typename T> 
class Pimpl 
{ 
    public: 
     Pimpl() 
     { 
      // At this point the object you have created is fully constructed. 
      // So now you can call the virtual method on it. 
      object.load(); 
     } 
     T* operator->() 
     { 
      // Use the pointer notation to get access to your object 
      // and its members. 
      return &object; 
     } 
    private: 
     T object; // Not technically a pointer 
         // But otherwise the pattern is the same. 
         // Modify to your needs. 
}; 

int main() 
{ 
    Pimpl<Derived> x; 
    x->doStuff(); 
} 
+0

Come posso usarlo e impedire che Derive venga creato da qualche altra parte? – Vargas

+1

Rendi privato il costruttore. E poi crea Pimpl un amico di Derivato e Pimpl un amico di Derivato2, ecc. –

+0

Senza bisogno di modificare ogni classe derivata, c'è un modo? Forse in qualche modo rendere Pimpl un amico di Base? – Vargas

0

Molti framework noti (come MFC) eseguono questa operazione: creano una funzione membro (virtuale) Init() o Create() e eseguono l'inizializzazione in quel punto e quindi impongono nella documentazione che l'utente chiama. So che non ti piacerà questa idea, ma non puoi semplicemente chiamare un metodo virtuale da un costruttore e aspettarti che si comporti polimorficamente, indipendentemente dai metodi purezza ...

6

Il problema è che la costruzione della classe base si verifica prima che la classe derivata sia completamente costruita. Si dovrebbe neanche chiamare "carico" dalla classe derivata, inizializzare motivi indipendenti dalla volontà di una diversa funzione membro virtuale o creare una funzione di supporto per fare questo:

Base* CreateDerived() 
{ 
    Base* pRet = new Derived; 
    pRet->Load(); 
    return pRet; 
} 
+0

Ok, sono riuscito ad arrivare fino a qui, ma come posso evitare che Derived venga creato elsewere e quindi utilizzato senza un'inizializzazione corretta? – Vargas

+0

@Vargas: Per ogni classe derivata da Base, rendere privato il costruttore e definire un 'Base * CreateDerivedX() 'e renderlo un amico. –

+0

@Eugen, questo può essere fatto senza dover cambiare ogni classe derivata? – Vargas

3

Il C++ FAQ chiama questo problema DBDI, Associazione dinamica durante la costruzione. Principalmente, il problema è evitare la costruzione in due fasi del male sostenuta in altre risposte qui. È una sorta di "mio" articolo FAQ - Ho convinto Marshall ad aggiungerlo.

Tuttavia, Marshall ci tiene conto che è molto generico (il che è positivo per una FAQ), mentre io ero più interessato al particolare modello di progettazione/codifica.

Quindi, invece di inviarti alle FAQ, ti mando al mio blog, l'articolo "How to avoid post-construction by using Parts Factories", che rimanda all'elemento della FAQ pertinente, ma discute in profondità il modello.

Si può solo saltare i primi due paragrafi ...

I sorta di divagare lì. :-)

Acclamazioni & hth.,

1

non si può aggiungere un metodo getLoader() nella classe Base in modo che DerivedClass costruttore può chiamare il this per ottenere un Loader? Come costruttore DerivedClass verrà chiamato dopo il costruttore della classe Base, che dovrebbe funzionare correttamente.

1

È difficile dare consigli a meno che non ci dica cosa stai cercando di realizzare, piuttosto che come. Trovo che di solito sia meglio costruire tali oggetti da una fabbrica, che caricherà i dati richiesti prima e poi passerà i dati nel costruttore dell'oggetto.

0

Ci sono molti modi per correggere questo, qui è 1 suggerimento raccordo all'interno del tuo quadro

struct Loader { 
    int get(int index) { return 0; } 
}; 

struct Base{ 
    Base() { 
    } 
    Loader & getLoader(); 
private: 
    Loader l; 
}; 

struct Derived: public Base { 
    int value; 
    Derived() { 
     value = getLoader().get(0); 
    } 
}; 
+0

Nota: il costruttore 'Derived' non è dichiarato correttamente. Inoltre, se l'istanza di 'Loader' è costosa e non dovrebbe essere attiva per troppo tempo (ad esempio la lettura da un file XML o JSON), questo sarà un problema. Tuttavia, i pollici in su per una soluzione compatibile con le versioni precedenti! –

+0

Certamente la vita dell'oggetto Loader cambia, si alza un buon punto sul potenziale costo associato. –

0

fornito Questo può venire un po 'tardi, dopo altre risposte, ma io ancora fare un tentativo.

È può implementare questa sicurezza, e senza cambiare classi derivate. Tuttavia, è necessario modificare utilizzando di tutte queste classi, che potrebbe essere molto peggio, a seconda del proprio scenario. Se stai ancora progettando, questa potrebbe essere un'alternativa praticabile.

Fondamentalmente, è possibile applicare curiously recurring template pattern e immettere il codice di inizializzazione dopo che il costruttore è stato richiamato. Inoltre, se lo fai come ho scritto qui sotto, puoi anche proteggere load da essere chiamato due volte.

struct Loader { 
    int get(int index) { return 0; } 
}; 
struct Base { 
    virtual ~Base() {} // Note: don't forget this. 
protected: 
    virtual void load(Loader &) = 0; 
}; 

struct Derived : public Base { 
    int value; 
protected: 
    void load(Loader &l) { 
     value = l.get(0); 
    } 
}; 

template<typename T> 
class Loaded : public T 
{ 
public: 
    Loaded() { 
     Loader l; T::load(l); 
    } 
}; 

int main (int, char **) 
{ 
    Loaded<Derived> derived; 
} 

Francamente, tuttavia, se possibile, considererei un progetto alternativo. Spostare il codice load ai vostri costruttori e fornire il caricatore come un argomento di riferimento inadempiente come segue:

struct Derived : public Base { 
    Derived (Loader& loader = Loader()) { ... } 
}; 

In questo modo, si evita completamente il problema.

Sommario: le scelte sono le seguenti:

  1. Se non si è limitata da vincoli esterni e non si ha una vasta base di codici a seconda questo, cambia il vostro disegno per qualcosa di più sicuro.
  2. Se si desidera mantenere load così com'è e non modificare troppo le classi ma si è disposti a pagare il prezzo di modifica di tutte le istanze, applicare CRTP come proposto sopra.
  3. Se si insiste per essere in gran parte retrocompatibili con il codice client esistente, sarà necessario modificare le classi per utilizzare un PIMPL come altri hanno suggerito o convivere con il problema esistente.
+0

Inoltre, se si desidera assicurarsi che gli oggetti non vengano creati senza essere caricati, dichiarare i costruttori come protetti. Questo, tuttavia, è * non * sicuro perché altri client possono semplicemente derivare dalla tua classe e fare tutto ciò che vogliono, incluso non chiamare 'load' o chiamarlo due volte. –

+0

é: first just a nit: il 2o esempio manca di 'const' (è un codice non valido come presentato). Sono d'accordo con la tua preoccupazione per la rimozione della costruzione a 2 fasi. Per un approccio più generale vedi la mia risposta precedente. E/o le domande frequenti su C++. Cheers & hth., –

+0

@ Alf: Ho lasciato il comando 'const' out perché nell'istruzione problema,' Loader :: get() 'non era' const'. Ma hai ragione, è una sintassi non valida e dovrei averlo messo lo stesso. Per quanto riguarda il tuo post, controllo anche il tuo blog. Ho usato lo stesso identico disegno nello stesso tipo di libreria, quindi sono assolutamente d'accordo. Ho pubblicato queste due alternative perché trovo che richiedono meno codice per scrivere. –

Problemi correlati