effect C++はvirtual関数を構築および構築中に呼び出さない

3826 ワード

base class構築中にvirtual関数はderived classes階層に低下しません.
class継承システムを使用して、購入、販売注文などの株式市場取引をモデル化します.トランザクション・オブジェクトを作成するたびに、監査ログに適切なレコードを作成する必要があります.
class Transaction{
public :
Transaction();
virtual void logTransaction()const = 0;       //                
...
}
Transaction::Transaction()
{
  ...
  logTransaction();			    //           
}
class BuyTransaction:public Transaction{ //derived class
public: virtual void logTransaction()const; //        
...};
class SellTransaction: public Transaction{//derived class
public: virtual void logTransaction() const; //       
...
}
 当执行  
    
    
   
BuyTransaction b;

BuyTransactionコンストラクション関数が呼び出されるのは間違いありませんが、まずTransactionコンストラクション関数がより早く呼び出されるに違いありません.はい、derived classオブジェクト内のbase class成分は、derived class自身の成分が関数を構築する前に適切に構築されます.Transactionコンストラクション関数の最後の行はvirtual関数logTransactionを呼び出し、呼び出されたlogTransactionはTransaction内のバージョンであり、BuyTransaction内のバージョンではありません.base class構築中にvirtual関数はderived classes階層に低下しません.
base classコンストラクション関数はderived classコンストラクション関数よりも早く実行されるため、base classコンストラクション関数が実行されるとderived classのメンバー変数は初期化されません.この期間に呼び出されたvirtual関数がderivedレイヤに下がると、(derived classの関数は必ずlocalメンバー変数を使用しますが、それらのメンバー変数はまだ初期化されていません)不明確な動作を引き起こします.
derived classオブジェクトのbase class構造中、オブジェクトのタイプはderived classではなくbase classです.virtual関数がコンパイラによってbase classに解析されるだけでなく、実行期間タイプ情報を使用すると、オブジェクトはbase classタイプとみなされます.
Transactionコンストラクション関数が「BuyTransactionオブジェクト内のbase class成分」を初期化する準備をしている場合、このオブジェクトのタイプはTransactionです.
このオブジェクト内の「BuyTransaction固有成分」はまだ初期化されていないので、それらに直面して、最も安全な方法はそれらが存在しないことを見ることです.オブジェクトはderived classコンストラクション関数が実行される前にdrived classオブジェクトにはなりません.
同じ理屈は構造関数にも適用される.derived class構造関数が実行されると、オブジェクト内のderived classメンバー変数は未定義の値を示します.base class解析関数に入るとオブジェクトはbase classオブジェクトになりますが、C++の任意の部分にはvirtual関数、dynamic_が含まれます.castsなどもそう見ています.
Transactionに複数のコンストラクション関数がある場合、それぞれが同じ作業を実行する必要がある場合、コードの重複を回避する優れた方法は、logTransactionの呼び出しを含む共通の初期化コードをinitなどの初期化関数に入れることです.
public:
Transaction()
 { init();}  	//  non-virtual...
 virtual void logTransaction()const=0;
  ...
private:
   void init()
   {
     ....
     logTransaction();  //    virtual
   }
}

この場合、logTransactionはTransaction内のpure virtual関数であるため、pure virtual関数が呼び出されると、多くの実行システムがプログラムを中止する.
しかし、logTransactionが通常のvirtual関数であり、Transaction内に実装コードがある場合、このバージョンが呼び出され、derived classオブジェクトが作成されるとエラーバージョンのlogTransactionが呼び出されます.
回避:オブジェクトが作成され、破棄された間にvirtual関数が呼び出されず、呼び出されたすべての関数も同じ制約に従っていることを確認します.
Transactionコンストラクション関数内でオブジェクトに対してvirtual関数を呼び出すのは誤った方法です.
class TransactionでlogTransaction関数をnon-virtualに変更し、derived classコンストラクション関数に必要な情報をTransactionコンストラクション関数に渡すように要求します.その後、そのコンストラクションはnon_を安全に呼び出すことができます.virtual logTransaction.
 
class Transaction{
public:
  explicit Transaction(const std::string&logInfo);
  void logTransaction(const std::string&logInfo)const;//non-virtual  
  ...
};
Transaction:: Transaction(const std::string&logInfo)
{
  ...
  logTransaction(logInfo);
}
class BuyTransaction:public Transaction{
public:
  BuyTransaction(parameters):Transaction(createLogString(parameters))// log    base class  
  {...}
  ...
  private:
  static std::string createLogString(parameters);
};

virtual関数を使用してbase classesから下に呼び出すことはできません.構築中にderived classesに必要な構築情報をbase class構築関数に伝達して補うしかありません.
注意BuyTransaction内のprivate static関数createLogString.
メンバーの初期値列にbase classに必要なデータを与えるよりも、補助関数を使用してbase classコンストラクション関数に値を作成するほうが便利で、読み取りやすいことが多い.この関数をstaticにすると、「初期未成熟のBuyTransactionオブジェクト内で初期化されていないメンバー変数」を意外に指すことはできません.「それらのメンバー変数は未定義の状態にある」ため、「base classの構築と解析中に呼び出されたvirtual関数はderived classesに低下してはいけません.」
このような呼び出しはderived class(現在コンストラクション関数とコンストラクション関数を実行しているレイヤよりも)に低下しないため、コンストラクションとコンストラクションの間virtual関数を呼び出さないでください.