2

現在作成しているアプリケーションでは、純粋仮想関数を使用してテンプレートクラスを作成し、次に前者のインスタンスを継承して仮想関数を実装する別のクラスを作成しました。仮想関数は、子によっても使用される親のコンストラクターから呼び出されます。リンカーエラーのためにこのコードをビルドできず、その理由がわかりません。これが私が抱えている問題を再現するためのコードの簡略版です。

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

リンカエラーは、MSVCでは次のように表示されます。

purevirttest.obj:エラーLNK2019:未解決の外部シンボル "protected:virtual void __thiscall Foo :: echo(void)"(?echo @?$ Foo @ H @@ MAEXXZ)関数 "public:__thiscall Foo :: Foo(int )"(?? 0?$ Foo @ H @@ QAE @ H @ Z)

Fooのコンストラクターからecho()の呼び出しを移動すると、コードが適切にビルドおよび実行され、bar.echo()を問題なく呼び出すことができます。問題は、コンストラクターでその関数が本当に欲しいということです。この謎の説明は大歓迎です。

4

2 に答える 2

5

echo()「のコンストラクターからは呼び出せない」というJamesMcNellisの答えFoo<T>ほぼ正しいです。

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

ただし、のような純粋仮想関数の実装を提供、コンストラクターからのecho()ように非仮想的に呼び出すことができます。:-)それが実装を呼び出すことを除いて。派生クラスの実装を呼び出したいようですが。Foo::echo()FooFoo

今あなたの問題に関して:

「コンストラクターでその関数が本当に欲しいのです。」

さて、私がこれを書いているとき、あなたの(無効な)コードは次のようになります:

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から継承するクラスの実装を呼び出すようにしFooます。

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

1つは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);
}

これらの2つのプログラムを研究すると、おそらく微妙な違いに気付くでしょう(コンパイル時と実行時の知識の伝達に加えて)。

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

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

ちなみに、あなたがインスタンスであると思われるより一般的な一連の問題は、DBDI初期化中の動的バインディングとして知られています。C ++ FAQアイテム23.6でより一般的な扱いを見つけることができますが、基本クラスのコンストラクター内で動的バインディングがこのオブジェクトで機能するかのように、その動作をシミュレートする方法はありますか?。また、DBDIで、基本クラスの構築の一部を派生クラスによって制御/提供する必要があるという特殊なケースについては、私のブログエントリ「パーツファクトリを使用して構築後を回避する方法」を参照してください。

乾杯&hth。、

于 2010-11-18T06:56:06.377 に答える
4

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

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

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

構築および破棄中にオブジェクトの動的タイプがどのように変化するかをよく理解していない限り、コンストラクタおよびデストラクタから仮想関数を呼び出さないことをお勧めします。

于 2010-11-18T02:07:57.560 に答える