基本クラスのコンストラクター内から派生仮想関数を呼び出すことができないため、想像どおりに実行することはできません。オブジェクトはまだ派生型ではありません。しかし、これを行う必要はありません。
MyBase 構築後に PrintStartMessage を呼び出す
次のようなことをしたいとしましょう。
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
。しかし一方で、それが本当に必要かどうかはよく考えてください。
MyBase 構築前に 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 番目の解決策は、Curiously Recurring Template Pattern (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"); }
};
専用のファクトリ関数を使用する必要なく、出力は期待どおりです。
CRTP を使用して PrintStartMessage 内から MyBase にアクセスする
が実行されている間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");
}
};
テンプレートなしのソリューション - 再設計
さらに別のオプションは、コードを少し再設計することです。PrintStartMessage
IMO これは、構造内からオーバーライドされたものを絶対に呼び出す必要がある場合に、実際に推奨されるソリューションです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);
}