2010-11-18 12 views
1

私が現在書いているアプリケーションでは、純粋な仮想関数を使ってテンプレートクラスを作成した後、仮想関数のインスタンスを継承し仮想関数を実装する別のクラスを作成しました。仮想関数は親のコンストラクタから呼び出され、子によっても使用されます。私はリンカのエラーのためにこのコードを構築することはできません。理由を理解できません。ここに私が持っている問題を再現するコードの簡略版があります。 (私がfooのコンストラクタから()エコーの呼び出しを移動した場合テンプレート内で純粋仮想関数を使用してC++コードをビルドしようとするとリンカエラーが発生するのはなぜですか?

purevirttest.obj : error LNK2019: unresolved external symbol "protected: virtual void __thiscall Foo::echo(void)" ([email protected][email protected]@@MAEXXZ) referenced in function "public: __thiscall Foo::Foo(int)" ([email protected]@@[email protected]@Z)

は、コードは私がbar.echo呼び出すことができ、構築し、うまく実行されます。MSVCで次のように

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; 
} 

リンカエラーが表示されます。 )問題なく。問題は、コンストラクタでその関数が本当に好きなのです。この神秘のどんな説明も高く評価されました。

答えて

4

James McNellisの答え「Foo<T>のコンストラクタからecho()を呼び出すことはできません」は、であり、ほぼです。

Foo<T>コンストラクタの本体がFoo<T>のオブジェクトを実行している間に、Foo<T>コンストラクタから仮想的に呼び出すことはできません。派生クラスパートはまだありません。 echo()の仮想呼び出しは、コード内でのように、純粋な仮想関数bang、deadに移動します。

ただし、echo()のような純粋仮想関数の実装を提供することができ、その後、Fooコンストラクタから、Foo::echo()のような、非事実上それを呼び出します。 :-)それ以外はFooの実装を呼び出します。あなたは派生クラスの実装を呼びたいと思うのですが。今すぐあなたの問題について

:私はこれはあなたの(不正な)コードを書いているように、

"I'd really like that function in the constructor."

さて、次のようになります。

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; 
} 

そして限り、私はあなたの問題の説明を理解し、 Fooコンストラクタが、から継承するすべてのクラスのecho実装を呼び出すようにします。

これを行うにはいくつかの方法があります。彼らはすべて、派生クラスの実装についての知識を基底クラスに持ち込むことに関するものです。

一つはCRTP不思議な経常テンプレートパターンとして知られており、それはこのように行くことができ、あなたの特定の問題に適用されます。

#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); 
} 

以上が悪いわけではないソリューションです、どちらも良いことではありません。実際の問題が基本クラスのコンストラクタから派生クラスの非静的メンバー関数を呼び出すことである場合、唯一の「良い」答えはJavaやC#です。 C++では意図的にサポートされていません。なぜなら、派生クラスオブジェクトで、未だ初期化されていないものにうっかりアクセスしようとするのは非常に簡単だからです。

とにかく、いつものように、何かのコンパイル時の解決策がある場所には、実行時の解決策もあります。

あなたは、単にそのように、コンストラクタの引数として実行される関数を渡すことができます。

#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); 
} 

あなたはこれら二つのプログラムを勉強すれば、あなたはおそらく、実行対時間をコンパイルするのに加えて、微妙な違いを(注意しましょう時間知識移転)。

最後に、ダーティー・キャストを含むソリューションがあります。また、メンバー・ポインターを使用して、キャストなしで保護された基本クラスの状態にアクセスできるC++タイプ・システムに抜け穴があります。前者は危険であり、後者は不明瞭であり、おそらく非効率的である。だから、しないでください。

しかし、うまくいけば、上記の解決策の1つがあなたに合っているか、または適切な適応策になるでしょう。

ああ、ところで、あなたはのインスタンスであるように思わ問題のより一般的なセットは、DBDI、動的初期化中にバインディングとして知られています。 C++ FAQの23.6 Okay, but is there a way to simulate that behavior as if dynamic binding worked on the this object within my base class's constructor?に、より一般的な扱いがあります。また、派生クラスによって基本クラス構築の一部を制御/提供するDBDIがある特別なケースについては、私のブログエントリ"How to avoid post-construction by using Parts Factories"を参照してください。

乾杯& HTH。、

4

Foo<T>のコンストラクタからecho()を呼び出すことはできません。

Foo<T>のコンストラクタ内では、オブジェクトの動的タイプはFoo<T>です。動的タイプがBarになるのは、Foo<T>コンストラクタが終了するまでです。 echo()以来

Foo<T>で仮想純粋でありFoo<T>は、オブジェクトの動的な型であるため、あなたはFoo<T>のコンストラクタでecho()を呼び出すことはできません。

コンストラクタやデストラクタから仮想関数を呼び出そうとしないことをお勧めします。

+0

私はあなたを-1'dか分からないが、あなたのコメントは右私に読み込みます。 G ++は次の警告を出力します: 'warning:abstract virtual 'void Foo :: echo()[T = int]'がコンストラクタから呼び出されました' ...そしてエラー: 'test.cpp :(。text._ZN3FooIiEC2Ei [ Foo :: Foo(int)] + 0x2c): 'Foo :: echo() ''への未定義の参照 – Ashe

+0

ディスパージングトーンは不要かもしれませんが、この回答は正しいので、私はそれをアップヴォーグしています。言葉の価値があるかもしれません - 基本的に、Barのコンストラクタがオブジェクト作成時にFooのコンストラクタを呼び出すとき、FooはBarについて何も知らない。オブジェクトが完全に作成されてからRTTIが起動し、適切な仮想関数を呼び出すことはできません。 – rcv

関連する問題