条項9:virtual関数を構築および構築中に呼び出さない
8387 ワード
株式取引の例は次のとおりです.
上記のコードでBuyTransaction bを実行すると、ベースクラスのコンストラクション関数が呼び出され、ベースクラスのコンストラクション関数は虚関数を呼び出して作業を完了します.しかし、この虚関数は派生クラスの対応する関数を呼び出しますか?答えは否定的で、解釈は以下の通りです.
1>ベースクラスの構造は派生クラスの構造より先である.ベースクラスのコンストラクション関数が実行されると、派生クラスのメンバーはまだ初期化されていません.ベースクラスのコンストラクション中に派生クラスの関数を呼び出すことを許可すると、初期化されていない「ゴミ値」が呼び出され、不確定な動作が発生する可能性があります.
2>ベースクラス構築中、virtual関数の動作は派生クラスに絶対に伸びません.このとき構築されるのはベースクラスです.すなわち、このとき構築されるオブジェクトのタイプは派生クラスではなくベースクラスです.これは重要です.
しかし、ベースクラスを構築する際に派生クラスにしかないパラメータ情報が必要になる場合があります.どのように解決しますか.
ベースクラスでlogTransaction関数を非虚関数に変更し、派生クラスを構築する際に必要なパラメータをベースクラスのコンストラクション関数に渡します.
以上から分かるように、ベースクラス構築時に派生クラスの情報を使用する場合は、派生クラス構築関数の初期化リストから関連パラメータを入力することができ、ベースクラス構築中にオブジェクトのタイプがベースクラスタイプであるため、虚関数実装を呼び出すことはできない.
解析関数についても同様であり,ベースクラスの解析は常に派生クラスの解析後に行われ,このとき派生クラスのオブジェクトは既に存在せず,虚関数を呼び出すと必然的に不確定な挙動を引き起こす.もちろん、ベースクラスの虚関数を実装して構造関数で呼び出すこともできますが、そうすると、なぜ非虚関数と定義しないのか、虚関数の意味は存在しません.
1 class Transaction //
2 {
3 public:
4 Transaction();
5 virtual void logTransaction() const = 0; //
6 };
7 Transaction::Transaction()
8 {
9 logTransaction(); // 10 }
11
12 class BuyTransaction : public Transaction //
13 {
14 virtual void logTransaction() const;
15 };
16 void BuyTransaction::logTransaction() const{ }
17
18 class SellTransaction : public Transaction //
19 {
20 virtual void logTransaction() const;
21 };
22 void SellTransaction::logTransaction() const{ }
23
24 int main()
25 {
26 BuyTransaction b;
27
28 return 0;
29 }
上記のコードでBuyTransaction bを実行すると、ベースクラスのコンストラクション関数が呼び出され、ベースクラスのコンストラクション関数は虚関数を呼び出して作業を完了します.しかし、この虚関数は派生クラスの対応する関数を呼び出しますか?答えは否定的で、解釈は以下の通りです.
1>ベースクラスの構造は派生クラスの構造より先である.ベースクラスのコンストラクション関数が実行されると、派生クラスのメンバーはまだ初期化されていません.ベースクラスのコンストラクション中に派生クラスの関数を呼び出すことを許可すると、初期化されていない「ゴミ値」が呼び出され、不確定な動作が発生する可能性があります.
2>ベースクラス構築中、virtual関数の動作は派生クラスに絶対に伸びません.このとき構築されるのはベースクラスです.すなわち、このとき構築されるオブジェクトのタイプは派生クラスではなくベースクラスです.これは重要です.
しかし、ベースクラスを構築する際に派生クラスにしかないパラメータ情報が必要になる場合があります.どのように解決しますか.
ベースクラスでlogTransaction関数を非虚関数に変更し、派生クラスを構築する際に必要なパラメータをベースクラスのコンストラクション関数に渡します.
1 #include <string>
2
3 class Transaction //
4 {
5 public:
6 explicit Transaction(const std::string& logInfo);
7 void logTransaction(const std::string& logInfo) const; //
8 };
9 Transaction::Transaction(const std::string& logInfo)
10 {
11 logTransaction(logInfo); //
12 }
13 void Transaction::logTransaction(const std::string& logInfo) const{ }
14
15 class BuyTransaction : public Transaction //
16 {
17 public:
18 BuyTransaction(const std::string& parameter) : Transaction(parameter){ } 19 };
20
21
22 int main()
23 {
24 BuyTransaction b("BT");
25
26 return 0;
27 }
以上から分かるように、ベースクラス構築時に派生クラスの情報を使用する場合は、派生クラス構築関数の初期化リストから関連パラメータを入力することができ、ベースクラス構築中にオブジェクトのタイプがベースクラスタイプであるため、虚関数実装を呼び出すことはできない.
解析関数についても同様であり,ベースクラスの解析は常に派生クラスの解析後に行われ,このとき派生クラスのオブジェクトは既に存在せず,虚関数を呼び出すと必然的に不確定な挙動を引き起こす.もちろん、ベースクラスの虚関数を実装して構造関数で呼び出すこともできますが、そうすると、なぜ非虚関数と定義しないのか、虚関数の意味は存在しません.