Come evitare l'inizializzazione in due passi (C ++)?

5

Mi piacerebbe seguire l'idioma RAII (l'acquisizione delle risorse è inizializzazione) per tutto il mio codice, ma sto anche facendo lo schema modello in cui sto sviluppando le versioni generiche delle mie classi e usandole per costruire un codice base comune per alcuni cose. A volte ho bisogno di forzare una sequenza di inizializzazione in cui avrei bisogno di chiamare le funzioni virtuali dell'oggetto specializzato nel costruttore ma ciò non è possibile in C ++. L'unica soluzione a cui riesco a pensare è un'inizializzazione in due passaggi chiamando una funzione init dopo che l'oggetto è stato creato ma che interrompe l'idioma RAII. C'è qualche soluzione a questo?

#include <memory>

class A {
public:
    A() {
        // I want to call B's foo() here
    }
    virtual void foo() = 0;
};

class B : public A {
public:
    B() {};
    virtual void foo() {};
};

void main() {
    std::unique_ptr<A> a(static_cast<A*>(new B));

    // Use b polymorphically from here...
}
    
posta navark 07.01.2017 - 01:34
fonte

3 risposte

5

Si potrebbe prendere l'approccio di rendere il costruttore di A protetto, il costruttore di B privato, e quindi creare un helper statico di classe per istanziare B; l'helper statico farebbe quindi l'inizializzazione a due stadi; questo assicurerebbe che l'istanziazione sia sempre eseguita correttamente e rendendo il costruttore privato / protetto assicuri di non tentare accidentalmente di istanziare direttamente la classe. Questo ha il rovescio della medaglia che non puoi istanziare la classe nello stack o come membro - deve essere sullo heap.

    
risposta data 07.01.2017 - 03:55
fonte
3

Il problema di base è qui:

A() {
    // I want to call B's foo() here
}

Perché vuoi chiamare B's foo () qui? Come può qualcosa di specifico per B essere rilevante per l'inizializzazione di A, che non ha alcuna idea di B? La risposta indicherà sempre un design scadente.

Una classe di risposte (abbastanza comune, ma apparentemente non pertinente qui) è quando viene utilizzato un metodo virtuale per restituire un valore specifico B che A memorizzerà e / o userà. Il concetto di design scarso qui è che dovrebbe essere usato un metodo virtuale per passare questo valore. Un'alternativa superiore consiste nel passare il valore dalla classe derivata come argomento al costruttore della classe base. In questo modo, la classe base non deve preoccuparsi del modo in cui si ottiene il valore.

Qui, tuttavia, foo () ha una firma di reso nulla. Qualunque cosa faccia, deve farlo nel contesto di B. Quindi, come può influire su A? Potrebbe avere accesso a un membro protetto o chiamare altri metodi di classe base. Ma perché una di queste azioni dovrebbe verificarsi durante l'inizializzazione di A? Perché non durante l'inizializzazione di B?

Il punto di base è che l'inizializzazione di A dovrebbe essere progettata per dipendere solo dai suoi parametri di costruzione. In questo modo, qualsiasi classe derivata può raggiungere la sua personalizzazione invocando il costruttore della classe base dal proprio costruttore con i valori appropriati.

APPENDICE: Per il modello di metodo del modello, il meglio che puoi fare è rafforzare la disciplina implicita, con un'organizzazione su queste linee:

class Base
{
public:
    virtual ~Base(); // for polymorphic destruction
protected: // to ensure invocation from derived classes only
    Base()
    {
        // pre-customization base class actions
    } 
    void finish_init() // virtual methods can be called from here!
    {
        // post-customization base class actions
    }
};

class Derived : public Base
{
public:
    Derived() : Base()
    {
        // derived class customized actions
        finish_init();
    }
};

Questo non è "a due fasi" in quanto l'inizializzazione della classe derivata avviene interamente all'interno del suo costruttore. Il modo in cui tale lavoro è suddiviso è interno all'implementazione e quindi "unitario" da qualsiasi prospettiva esterna.

    
risposta data 13.01.2019 - 23:22
fonte
2

Non puoi chiamare le funzioni di B dal costruttore di A , perché B subobject non esiste ancora .
Qualsiasi progetto che si baserebbe su una chiamata di funzione virtuale da un costruttore principale non ha senso.

La soluzione è semplicemente chiamare foo() dal costruttore di B .

    
risposta data 11.01.2017 - 15:06
fonte

Leggi altre domande sui tag