Perché ricevo errori del linker che provano a costruire usando le funzioni virtuali pure all'interno di un modello C ++?

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

  •  25-09-2019
  •  | 
  •  

Domanda

In un'applicazione Attualmente sto scrivendo, ho creato una classe template con una funzione virtuale pura, poi un altro classe che eredita un'istanza del primo e l'attuazione della funzione virtuale. La funzione virtuale viene chiamata dal costruttore del genitore, che viene utilizzato dal bambino anche. Non posso costruire questo codice a causa di errori del linker e non riesco a capire perché. Ecco una versione semplificata del codice di riprodurre il problema che sto avendo.

template <typename T> class Foo
{
public:
    Foo(T a)
    {
        x = a;
        echo();
    }

protected:
    T x;
    virtual void echo() = 0;
};

class Bar : public Foo<int>
{
public:
    Bar(int a) : Foo<int>(a)
    {
    }

    void echo();
};

void Bar::echo()
{
    cout << "value: " << x << endl;
}

int main(int argc, char* argv[])
{
    Bar bar(100);
    return 0;
}

L'errore di linker si presenta come segue in MSVC:

  

purevirttest.obj: LNK2019 di errore: simbolo esterno non risolto "protetta: virtual vuoto __thiscall Foo :: echo (void)" fa riferimento in funzione "pubblica (echo $ @ Foo @ H @@ MAEXXZ??): __Thiscall Foo :: foo (int)"(?? 0? $ pippo @ H @@ QAE @ H @ Z)

Se sposto la chiamata a echo () dal costruttore di Foo, il codice costruisce ed esegue bene, posso chiamare bar.echo () senza alcun problema. Il problema è che mi piacerebbe molto che la funzione nel costruttore. Ogni spiegazione di questo mistero altamente apprezzato.

È stato utile?

Soluzione

James McNellis' risposta che "Non si può chiamare echo() dal costruttore di Foo<T>" è quasi corretta.

Non è possibile chiamare praticamente da un costruttore Foo<T>, perché mentre il corpo del costruttore Foo<T> esegue l'oggetto è di tipo Foo<T>. Non c'è ancora derivano parte della classe. E una chiamata virtuale di echo(), come nel codice, poi va a pura funzione virtuale: bang, morti

.

Tuttavia, è possono fornire un'implementazione di una funzione virtuale pura come echo(), e poi lo chiamano non virtualmente, come Foo::echo(), dal costruttore Foo. :-) Solo che che chiama l'implementazione Foo. Mentre sembra che desideri chiamare l'implementazione classe derivata.

Ora per quanto riguarda il problema:

  

"Mi piacerebbe molto che funzione nel   costruttore ".

Bene, come sto scrivendo questo il tuo (non valido) sembra codice come questo:

template <typename T> class Foo
{
public:
    Foo(T a)
    {
        x = a;
        echo();
    }

protected:
    T x;
    virtual void echo() = 0;
};

class Bar : public Foo<int>
{
public:
    Bar(int a) : Foo<int>(a)
    {
    }

    void echo();
};

void Bar::echo()
{
    cout << "value: " << x << endl;
}

int main(int argc, char* argv[])
{
    Bar bar(100);
    return 0;
}

E per quanto ho capito la tua descrizione del problema, si vuole il costruttore Foo di chiamare l'attuazione echo di qualsiasi classe è che eredita da Foo.

Ci sono un certo numero di modi per farlo; sono tutti di portare la conoscenza della realizzazione classe derivata alla classe di base.

Una è nota come CRTP , il Curiosamente ricorrente motivo template , e adattato al vostro problema particolare può andare in questo modo:

#include <iostream>

template< class XType, class Derived >
class Foo
{
public:
    Foo( XType const& a )
        : state_( a )
    {
        Derived::echo( state_ );
    }

protected:
    struct State
    {
        XType   x_;
        State( XType const& x ): x_( x ) {}
    };

private:
    State   state_;
};

class Bar
    : public Foo< int, Bar >
{
private:
    typedef Foo< int, Bar >     Base;
public:
    Bar( int a ): Base( a ) {}
    static void echo( Base::State const& );
};

void Bar::echo( Base::State const& fooState )
{
    using namespace std;
    cout << "value: " << fooState.x_ << endl;
}

int main()
{
    Bar bar(100);
}

È possibile che questo è una soluzione che non è male, ma non è buono neanche. Se il problema reale è quello di chiamare una funzione membro della classe derivata non statica dal costruttore della classe base, allora l'unica risposta "buona" è Java o C #, che consentono di fare una cosa simile. Non è intenzionalmente supportato in C ++, perché è molto facile poi inavvertitamente tenta di accedere non ancora roba non inizializzato nell'oggetto classe derivata.

In ogni modo, come quasi sempre, dove c'è una soluzione tempo di compilazione di qualcosa, c'è anche una soluzione fase di esecuzione.

Si può semplicemente passare la funzione che deve essere eseguito come un argomento del costruttore, in questo modo:

#include <iostream>

template< class XType >
class Foo
{
protected:
    struct State
    {
        XType   x_;
        State( XType const& x ): x_( x ) {}
    };

public:
    Foo( XType const& a, void (*echo)( State const& ) )
        : state_( a )
    {
        echo( state_ );
    }

private:
    State   state_;
};

class Bar
    : public Foo< int >
{
private:
    typedef Foo< int >  Base;
public:
    Bar( int a ): Base( a, echo ) {}
    static void echo( Base::State const& );
};

void Bar::echo( Base::State const& fooState )
{
    using namespace std;
    cout << "value: " << fooState.x_ << endl;
}

int main()
{
    Bar bar(100);
}

Se si studia quei due programmi che probabilmente notare una sottile differenza (in aggiunta al momento della compilazione rispetto al trasferimento di conoscenze fase di esecuzione).

Infine, ci sono soluzioni che coinvolgono calchi sporchi e c'è anche una lacuna nel sistema di tipo C ++ che consente di accedere che protetto Stato di classe base senza fusione, utilizzando puntatori membri. Il primo è pericoloso e quest'ultimo è oscuro e possibilmente inefficiente. Quindi, non lo fanno.

Ma si spera che una delle soluzioni sopra fa per voi, o qualche adattamento adeguato.

Oh, a proposito, il set più generale dei problemi che il vostro sembra essere un caso di, è conosciuto come DBDI , binding dinamico durante l'inizializzazione . È possibile trovare un trattamento più generale nella voce C ++ FAQ 23.6 Va bene, ma c'è un modo per simulare il comportamento come se binding dinamico ha lavorato sul questo oggetto all'interno di mia classe di base costruttore? . Inoltre, per il caso particolare in cui la DBDI è che si vuole una parte della costruzione classe base da controllare / fornito dalla classe derivata, vedere il mio blog "Come evitare post-costruzione utilizzando parti di fabbriche" .

Saluti e hth.,

Altri suggerimenti

Non è possibile chiamare echo() dal costruttore di Foo<T>.

All'interno del costruttore della Foo<T>, tipo dinamico dell'oggetto è Foo<T>. Non è fino a dopo le finiture costruttore Foo<T> che il tipo di dinamica diventa Bar.

Dal echo() è puro virtuale in Foo<T> e poiché Foo<T> è il tipo dinamico dell'oggetto, non è possibile chiamare echo() nel costruttore di Foo<T>.

A meno che non avete molta familiarità con il modo il tipo dinamico di un oggetto cambia durante la costruzione e distruzione, sarebbe una buona idea di non provare a chiamare funzioni virtuali da costruttori e distruttori.

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