2009-07-20 14 views
9

A causa dei ben noti problemi con la chiamata di metodi virtuali da parte di costruttori e distruttori, di solito finisco con classi che necessitano di un metodo di setup finale da chiamare subito dopo il loro costruttore, e un metodo pre-teardown da chiamare solo prima della loro distruttore, come questo:Esiste un modo automatico per implementare le chiamate al metodo virtuale post-costruttore e pre-distruttore?

MyObject * obj = new MyObject; 
obj->Initialize(); // virtual method call, required after ctor for (obj) to run properly 
[...] 
obj->AboutToDelete(); // virtual method call, required before dtor for (obj) to clean up properly 
delete obj; 

questo funziona, ma porta con sé il rischio che il chiamante dimenticare di chiamare uno o entrambi questi metodi al momento opportuno.

Quindi la domanda è: c'è un modo in C++ per ottenere quei metodi da chiamare automaticamente, quindi il chiamante non deve ricordarsi di chiamarli? (Immagino che non ci sia, ma ho pensato di chiederlo comunque nel caso ci sia un modo intelligente per farlo)

+0

Che problema hai con distruttori? – peterchen

+4

Forse dovresti descrivere il tuo problema reale, forse in realtà non hai * bisogno * di queste chiamate ... – peterchen

+2

se hai "comunemente" bisogno di chiamare metodi virtuali da medici o da medici, sembra che tu abbia un grosso problema di progettazione. Puoi dare un esempio di classe in cui è necessario? Molto probabilmente, c'è una soluzione più semplice. (Come al solito, mi aspetto che RAII risolva il problema. Delega il problema su o più variabili membro, con i propri operatori/medici che eseguono ciascuno la propria parte di inizializzazione/smontaggio. – jalf

risposta

1

Il problema principale con l'aggiunta di post-costruttori per C++ è che nessuno si è ancora stabilito come trattare con i post-post-costruttori, post-post-post-costruttori, ecc

La teoria di fondo è che gli oggetti hanno invarianti. Questo invariante è stabilito dal costruttore. Una volta stabilito, i metodi di quella classe possono essere chiamati. Con l'introduzione di progetti che richiedono post-costruttori, si introducono situazioni in cui gli invarianti di classe non si stabiliscono una volta eseguito il costruttore. Pertanto, sarebbe ugualmente pericoloso consentire chiamate a funzioni virtuali da post-costruttori, e si perde immediatamente l'unico vantaggio apparente che sembravano avere.

Come vostro esempio spettacoli (probabilmente senza rendersene conto), non sono necessari:

MyObject * obj = new MyObject; 
obj->Initialize(); // virtual method call, required after ctor for (obj) to run properly 

obj->AboutToDelete(); // virtual method call, required before dtor for (obj) to clean up properly 
delete obj; 

Mostriamo perché questi metodi non sono necessari. Queste due chiamate possono richiamare funzioni virtuali da MyObject o una delle sue basi. Tuttavia, MyObject::MyObject() può tranquillamente chiamare anche quelle funzioni. Non succede nulla dopo i ritorni MyObject::MyObject() che renderebbero sicuro obj->Initialize(). Quindi, obj->Initialize() è errato o la sua chiamata può essere spostata su MyObject::MyObject(). La stessa logica si applica al contrario a obj->AboutToDelete(). Il distruttore più derivato verrà eseguito per primo e può ancora chiamare tutte le funzioni virtuali, incluso AboutToDelete().

+5

Tranne quando Initialize() viene reimplementato in una sottoclasse di MyObject e ho bisogno di chiamare l'implementazione della sottoclasse, non MyObject :: Initialize(). Chiamato dal costruttore MyObject, non fa ciò che mi serve per farlo. (AboutToDelete() ha lo stesso problema quando chiamato da MyObject :: ~ MyObject()) In ogni caso, la "cosa che accade dopo MyObject :: MyObject() restituisce" è l'esecuzione dei costruttori della sottoclasse ... quelli che devono si verifica prima dell'esecuzione di Initialize(). La logica è invertita per AboutToDelete(), che deve essere eseguito prima dell'esecuzione di qualsiasi distruttore di sottoclassi. –

+0

Ovviamente non è il caso qui poiché 'new MyObject' precede direttamente la chiamata. E il tuo contro-esempio cambia semplicemente i nomi. Il costruttore più derivato viene eseguito per ultimo, quando tutti gli invarianti sono stati stabiliti e tutte le funzioni virtuali possono essere richiamate. Quel ctor può ancora chiamare Initialize() – MSalters

+4

Il costruttore più derivato non può chiamare in modo sicuro Initialize(), perché non può sapere con certezza che è il costruttore più derivato. Molto bene potrebbe essere che un'altra classe l'abbia sottoclassi, e in quel caso Initialize() sarebbe stato chiamato troppo presto. –

2

Ho usato un metodo di fabbrica Create() molto accurato (membro statico di ogni classe) chiamare una coppia di costruttore e inizializzatore nello stesso ordine in cui C# inizializza i tipi. Ha restituito un shared_ptr a un'istanza del tipo, garantendo un'allocazione dell'heap. Si è dimostrato affidabile e coerente nel tempo.

Il trucco: ho generato il mio C++ dichiarazioni di classe da XML ...

+0

Suppongo che tu abbia fornito un deleter personalizzato a 'shared_ptr', che includeva la chiamata alla logica di pre-distruzione? –

3

Il meglio che posso pensare è per voi di implementare il proprio puntatore intelligente con un metodo Create statico che le notizie di un istanza e chiama Initialize e nel suo distruttore chiama AboutToDelete e quindi elimina.

9

Anche se non esiste un modo automatico, è possibile forzare gli utenti a negare agli utenti l'accesso al distruttore su quel tipo e dichiarare un metodo di eliminazione speciale. In questo metodo puoi fare le chiamate virtuali che desideri. La creazione può adottare un approccio simile a quello di un metodo statico di fabbrica.

class MyObject { 
    ... 
public: 
    static MyObject* Create() { 
    MyObject* pObject = new MyObject(); 
    pObject->Initialize(); 
    return pObject; 
    } 
    Delete() { 
    this->AboutToDelete(); 
    delete this; 
    } 
private: 
    MyObject() { ... } 
    virtual ~MyObject() { ... } 
}; 

Ora non è possibile chiamare "delete obj;" a meno che il sito di chiamata non abbia accesso ai membri privati ​​MyObject.

+0

Mi piace, +1! –

+1

Il distruttore può (e dovrebbe) essere virtuale, perché passare attraverso il lavoro extra? –

+0

@dribeas, l'ho aggiornato per renderlo virtuale. – JaredPar

1

Fatta eccezione per l'idea di JavedPar per il metodo di pre-distruzione, non esiste una soluzione predefinita per eseguire facilmente la costruzione/distruzione in due fasi in C++. Il modo più ovvio per farlo è seguire la più comune risposta ai problemi in C++: "Aggiungi un altro livello di riferimento indiretto". È possibile avvolgere oggetti di questa gerarchia di classi all'interno di un altro oggetto. I costruttori/distruttori di quell'oggetto potrebbero quindi chiamare questi metodi. Esaminare l'idioma di Couplien con lettere e buste, ad esempio, o utilizzare l'approccio puntatore intelligente già suggerito.

2

http://www.research.att.com/~bs/wrapper.pdf Questa carta di Stroustrup risolverà il tuo problema.

Ho provato questo sotto VS 2008 e su UBUNTU contro il compilatore g ++. Ha funzionato bene.

#include <iostream> 

using namespace std; 

template<class T> 

class Wrap 
{ 
    typedef int (T::*Method)(); 
    T* p; 
    Method _m; 
public: 
    Wrap(T*pp, Method m): p(pp), _m(m) { (p->*_m)(); } 
    ~Wrap() { delete p; } 
}; 

class X 
{ 
public: 
    typedef int (*Method)(); 
    virtual int suffix() 
    { 
     cout << "X::suffix\n"; 
     return 1; 
    } 

    virtual void prefix() 
    { 
     cout << "X::prefix\n"; 
    } 

    X() { cout << "X created\n"; } 

    virtual ~X() { prefix(); cout << "X destroyed\n"; } 

}; 

class Y : public X 
{ 
public: 
    Y() : X() { cout << "Y created\n"; } 
    ~Y() { prefix(); cout << "Y destroyed\n"; } 
    void prefix() 
    { 
     cout << "Y::prefix\n"; 
    } 

    int suffix() 
    { 
     cout << "Y::suffix\n"; 
     return 1; 
    } 
}; 

int main() 
{ 
    Wrap<X> xx(new X, &X::suffix); 
    Wrap<X>yy(new Y, &X::suffix); 
} 
+0

+1 Articolo molto interessante. Tuttavia questo sembra riguardare solo metodi standard non costruttori e distruttori. – iain

0

Non ho ancora visto la risposta, ma le classi base sono solo un modo per aggiungere codice in una gerarchia di classi. È inoltre possibile creare classi progettate da aggiungere verso l'altro lato della gerarchia:

template<typename Base> 
class Derived : public Base { 
    // You'd need C++0x to solve the forwarding problem correctly. 
    Derived() : Base() { 
     Initialize(); 
    } 
    template<typename T> 
    Derived(T const& t): Base(t) { 
     Initialize(); 
    } 
    //etc 
private: 
    Initialize(); 
}; 
1

sono rimasto bloccato con lo stesso problema, e dopo un po 'di ricerca, credo che non c'è alcuna soluzione standard.

I suggerimenti che mi sono piaciuti di più sono quelli forniti in Aleksandrescu et al. libro "C++ standard di codifica" alla voce 49.

loro (fair use) Citando, sono disponibili diverse opzioni:

  1. Proprio lo documentano che avete bisogno di un secondo metodo, come hai fatto tu.
  2. avere un altro stato interno (un valore booleano) che le bandiere se post-costruzione ha avuto luogo
  3. utilizzare la semantica di classe virtuale, nel senso che il costruttore della classe più derivata da decide quali classe di base da utilizzare
  4. Usa una funzione di fabbrica.

Vedere il suo libro per i dettagli.

1

È possibile utilizzare il modello di funzione statico nella classe. Con ctor/dtor privato. Esegui comunità vs2015

class A { 
    protected: 
    A() {} 
     virtual ~A() {} 
     virtual void onNew() = 0; 
     virtual void onDelete() = 0; 
    public: 

     void destroy() { 
      onDelete(); 
      delete this; 
     } 

     template <class T> static T* create() { 
      static_assert(std::is_base_of<A, T>::value, "T must be a descendant of A"); 
      T* t = new T(); 
      t->onNew(); 
      return t; 
     } 
    }; 

class B: public A { 
    friend A; 

    protected: 
      B() {} 
      virtual ~B() {} 

      virtual void onNew() override { 
      } 

      virtual void onDelete() override { 
      } 
}; 

int main() { 
    B* b; 
    b = A::create<B>(); 
    b->destroy(); 
} 
Problemi correlati