純粋な仮想関数を含む基本クラスMyBaseがあります。
void PrintStartMessage() = 0
派生クラスごとにコンストラクタでそれを呼び出すようにしたい
それから私はそれを基本クラス(MyBase
)コンストラクタに入れます
class MyBase
{
public:
virtual void PrintStartMessage() =0;
MyBase()
{
PrintStartMessage();
}
};
class Derived:public MyBase
{
public:
void PrintStartMessage(){
}
};
void main()
{
Derived derived;
}
しかし、リンカエラーが発生します。
this is error message :
1>------ Build started: Project: s1, Configuration: Debug Win32 ------
1>Compiling...
1>s1.cpp
1>Linking...
1>s1.obj : error LNK2019: unresolved external symbol "public: virtual void __thiscall MyBase::PrintStartMessage(void)" (?PrintStartMessage@MyBase@@UAEXXZ) referenced in function "public: __thiscall MyBase::MyBase(void)" (??0MyBase@@QAE@XZ)
1>C:\Users\Shmuelian\Documents\Visual Studio 2008\Projects\s1\Debug\s1.exe : fatal error LNK1120: 1 unresolved externals
1>s1 - 2 error(s), 0 warning(s)
すべての派生クラスに強制したい...
A- implement it
B- call it in their constructor
どうすればよいですか?
そのオブジェクトがまだ構築されている間に派生から純粋な抽象メソッドを呼び出そうとすることは安全ではありません。それは車にガスを入れようとするようなものですが、その車はまだ組立ラインにあり、ガスタンクはまだ投入されていません。
そのようなことを行うのに最も近い方法は、最初にオブジェクトを完全に構築し、その後でメソッドを呼び出すことです。
template <typename T>
T construct_and_print()
{
T obj;
obj.PrintStartMessage();
return obj;
}
int main()
{
Derived derived = construct_and_print<Derived>();
}
基本クラスコンストラクター内から派生仮想関数を呼び出すことができないため、想像どおりにそれを行うことはできません。オブジェクトはまだ派生型ではありません。しかし、これを行う必要はありません。
次のようなことをしたいとしましょう:
class MyBase {
public:
virtual void PrintStartMessage() = 0;
MyBase() {
printf("Doing MyBase initialization...\n");
PrintStartMessage(); // ⚠ UB: pure virtual function call ⚠
}
};
class Derived : public MyBase {
public:
virtual void PrintStartMessage() { printf("Starting Derived!\n"); }
};
つまり、必要な出力は次のとおりです。
Doing MyBase initialization...
Starting Derived!
しかし、これがまさにコンストラクターの目的です!仮想関数をスクラップしてDerived
のコンストラクターにジョブを実行させるだけです。
class MyBase {
public:
MyBase() { printf("Doing MyBase initialization...\n"); }
};
class Derived : public MyBase {
public:
Derived() { printf("Starting Derived!\n"); }
};
出力は、まあ、私たちが期待するものです:
Doing MyBase initialization...
Starting Derived!
ただし、これは、派生クラスがPrintStartMessage
機能を明示的に実装することを強制しません。しかし、その一方で、いずれにしても空の実装を常に提供できるため、それが必要かどうかをよく考えてください。
上記のように、PrintStartMessage
が構築される前にDerived
を呼び出したい場合、Derived
のPrintStartMessage
オブジェクトがまだないため、これを実行できません。 _呼び出される。 PrintStartMessage
データメンバーのいずれにもアクセスできないため、Derived
が非静的メンバーであることを要求しても意味がありません。
または、次のように静的メンバーにすることもできます。
class MyBase {
public:
MyBase() {
printf("Doing MyBase initialization...\n");
}
};
class Derived : public MyBase {
public:
static void PrintStartMessage() { printf("Derived specific message.\n"); }
};
それがどのように呼ばれるかについて自然な疑問が生じますか?
私が見ることができる2つの解決策があります。1つは@greatwolfの解決策に似ており、手動で呼び出す必要があります。しかし、これは静的メンバーなので、MyBase
のインスタンスが構築される前に呼び出すことができます。
template<class T>
T print_and_construct() {
T::PrintStartMessage();
return T();
}
int main() {
Derived derived = print_and_construct<Derived>();
}
出力は
Derived specific message.
Doing MyBase initialization...
このアプローチでは、すべての派生クラスがPrintStartMessage
を実装する必要があります。残念ながら、ファクトリ関数でそれらを構築する場合にのみ当てはまります...これは、このソリューションの大きな欠点です。
2番目の解決策は、不思議な繰り返しのテンプレートパターン(CRTP)に頼ることです。コンパイル時に完全なオブジェクトタイプをMyBase
に通知することにより、コンストラクター内から呼び出しを実行できます。
template<class T>
class MyBase {
public:
MyBase() {
T::PrintStartMessage();
printf("Doing MyBase initialization...\n");
}
};
class Derived : public MyBase<Derived> {
public:
static void PrintStartMessage() { printf("Derived specific message.\n"); }
};
専用のファクトリー関数を使用する必要なく、出力は期待どおりです。
MyBase
が実行されている間、そのメンバーにアクセスすることはすでにOKです。それを呼び出したPrintStartMessage
にMyBase
がアクセスできるようにすることができます:
template<class T>
class MyBase {
public:
MyBase() {
T::PrintStartMessage(this);
printf("Doing MyBase initialization...\n");
}
};
class Derived : public MyBase<Derived> {
public:
static void PrintStartMessage(MyBase<Derived> *p) {
// We can access p here
printf("Derived specific message.\n");
}
};
以下も有効であり、非常に頻繁に使用されますが、少し危険です。
template<class T>
class MyBase {
public:
MyBase() {
static_cast<T*>(this)->PrintStartMessage();
printf("Doing MyBase initialization...\n");
}
};
class Derived : public MyBase<Derived> {
public:
void PrintStartMessage() {
// We can access *this member functions here, but only those from MyBase
// or those of Derived who follow this same restriction. I.e. no
// Derived data members access as they have not yet been constructed.
printf("Derived specific message.\n");
}
};
さらに別のオプションは、コードを少し再設計することです。 IMOこれは、オーバーライドされたPrintStartMessage
をMyBase
構造内から絶対に呼び出す必要がある場合に、実際に推奨されるソリューションです。
この提案は、次のようにDerived
をMyBase
から分離することです。
class ICanPrintStartMessage {
public:
virtual ~ICanPrintStartMessage() {}
virtual void PrintStartMessage() = 0;
};
class MyBase {
public:
MyBase(ICanPrintStartMessage *p) : _p(p) {
_p->PrintStartMessage();
printf("Doing MyBase initialization...\n");
}
ICanPrintStartMessage *_p;
};
class Derived : public ICanPrintStartMessage {
public:
virtual void PrintStartMessage() { printf("Starting Derived!!!\n"); }
};
MyBase
は次のように初期化します。
int main() {
Derived d;
MyBase b(&d);
}
コンストラクタでvirtual
関数を呼び出さないでください。 期間 。 PrintStartMessage
を非virtual
にして、すべてのコンストラクターで明示的に呼び出しを行うなど、いくつかの回避策を見つける必要があります。
これは古い質問であることはわかっていますが、プログラムの作業中に同じ質問に遭遇しました。
目的が、Baseクラスに共有初期化コードを処理させることによってコードの重複を減らす一方で、派生クラスに純粋な仮想メソッドでそれらに固有のコードを指定することを要求する場合、これを決定しました。
#include <iostream>
class MyBase
{
public:
virtual void UniqueCode() = 0;
MyBase() {};
void init(MyBase & other)
{
std::cout << "Shared Code before the unique code" << std::endl;
other.UniqueCode();
std::cout << "Shared Code after the unique code" << std::endl << std::endl;
}
};
class FirstDerived : public MyBase
{
public:
FirstDerived() : MyBase() { init(*this); };
void UniqueCode()
{
std::cout << "Code Unique to First Derived Class" << std::endl;
}
private:
using MyBase::init;
};
class SecondDerived : public MyBase
{
public:
SecondDerived() : MyBase() { init(*this); };
void UniqueCode()
{
std::cout << "Code Unique to Second Derived Class" << std::endl;
}
private:
using MyBase::init;
};
int main()
{
FirstDerived first;
SecondDerived second;
}
出力は次のとおりです。
Shared Code before the unique code
Code Unique to First Derived Class
Shared Code after the unique code
Shared Code before the unique code
Code Unique to Second Derived Class
Shared Code after the unique code
PrintStartMessage()が純粋な仮想関数ではなく通常の仮想関数である場合、コンパイラはそれについて文句を言わないでしょう。ただし、PrintStartMessage()の派生バージョンが呼び出されない理由を理解する必要があります。
派生クラスは自身のコンストラクターの前に基本クラスのコンストラクターを呼び出すため、派生クラスは基本クラスのように動作し、基本クラスの関数を呼び出します。
テンプレートではなくMACROSを使用するか、純粋に言語の「自然な」制約内に留まるようにして、抽象基本クラスに回避策/「コンパニオン」を提供できます。
Init関数を使用して基本クラスを作成します。例:
class BaseClass
{
public:
BaseClass(){}
virtual ~BaseClass(){}
virtual void virtualInit( const int i=0 )=0;
};
次に、コンストラクターのマクロを追加します。ここに複数のコンストラクター定義を追加しない理由や、複数のマクロから選択しない理由はありません。
#define BASECLASS_INT_CONSTRUCTOR( clazz ) \
clazz( const int i ) \
{ \
virtualInit( i ); \
}
最後に、派生にマクロを追加します。
class DervivedClass : public BaseClass
{
public:
DervivedClass();
BASECLASS_INT_CONSTRUCTOR( DervivedClass )
virtual ~DervivedClass();
void virtualInit( const int i=0 )
{
x_=i;
}
int x_;
};