次の例で「0」が出力されるのはなぜですか?また、「1」を出力するために何を変更する必要があるのですか?
#include <iostream>
struct base {
virtual const int value() const {
return 0;
}
base() {
std::cout << value() << std::endl;
}
virtual ~base() {}
};
struct derived : public base {
virtual const int value() const {
return 1;
}
};
int main(void) {
derived example;
}
base
が最初に構築され、まだderived
に「成熟」していないためです。オブジェクトが既に適切に初期化されていることを保証できない場合、オブジェクトのメソッドを呼び出すことはできません。
派生オブジェクトが構築されるとき、派生クラスコンストラクターの本体が呼び出される前に、基本クラスコンストラクターが完了する必要があります。派生クラスコンストラクターが呼び出される前は、構築中のオブジェクトの動的型は、派生クラスインスタンスではなく、基本クラスインスタンスです。このため、コンストラクターから仮想関数を呼び出す場合、基本クラスの仮想関数オーバーライドのみを呼び出すことができます。
実際、この動作を取得する方法があります。 「ソフトウェアの問題はすべて、間接的なレベルで解決できます。」
/* Disclaimer: I haven't done C++ in many months now, there might be a few syntax errors here and there. */
class parent
{
public:
parent( ) { /* nothing interesting here. */ };
protected:
struct parent_virtual
{
virtual void do_something( ) { cout << "in parent."; }
};
parent( const parent_virtual& obj )
{
obj.do_something( );
}
};
class child : public parent
{
protected:
struct child_virtual : public parent_virtual
{
void do_something( ) { cout << "in child."; }
};
public:
child( ) : parent( child_virtual( ) ) { }
};
多態的にコンストラクタから仮想メソッドを呼び出さないでください。代わりに、オブジェクトの構築後にそれらを呼び出すことができます。
コードは次のように書き直すことができます
struct base {
virtual const int value() const {
return 0;
}
base() {
/* std::cout << value() << std::endl; */
}
virtual ~base() {}
};
struct derived : public base {
virtual const int value() const {
return 1;
}
};
int main(void) {
derived example;
std::cout << example.value() << std::endl;
}
どのように機能するかという質問FAQアイテムです。
要約すると、クラスT
が構築されている間、動的な型はT
であり、派生クラス関数実装への仮想呼び出しを防ぎます。 JavaおよびC#の一般的な問題ですが、この点ではC++は安全です)。
基本クラスコンストラクターで派生クラス固有の初期化を行う方法の問題もa FAQ item、前述したものの直後。
要約すると、静的または動的なポリモーフィズムを使用して、関連する関数実装を基本クラスコンストラクター(またはクラス)に渡すことができます。
特定の方法の1つは、「パーツファクトリー」オブジェクトを渡すことです。この引数はデフォルトにすることができます。たとえば、一般的なButton
クラスは、ボタン作成API関数をそのWidget
基本クラスコンストラクターに渡して、そのコンストラクターが正しいAPIレベルオブジェクトを作成できるようにします。
一般的なルールは、コンストラクターから仮想関数を呼び出さないことです。