クラス コンストラクターの初期化処理の順序は、Visual C++ 2010 では C++ マネージ拡張から変更されています。
コンストラクターの初期化処理の順序比較
C++ のマネージ拡張では、コンストラクターの初期化処理は、次の順序で行われていました。
基本クラスのコンストラクターが存在する場合、そのコンストラクターが呼び出されます。
クラスの初期化リストが評価されます。
クラス コンストラクターのコード本体が実行されます。
この実行順序は、ネイティブ C++ プログラミングの慣例に従っています。 新しい Visual C++ 言語では、CLR クラスについて、次の実行順序が適用されます。
クラスの初期化リストが評価されます。
基本クラスのコンストラクターが存在する場合、そのコンストラクターが呼び出されます。
クラス コンストラクターのコード本体が実行されます。
この変更が該当するのは CLR のクラスだけです。Visual C++ 2010 のネイティブ クラスについては、従来と同じ規則が適用されます。 どちらの場合も、これらの規則は、特定のクラスの階層全体を上にたどって適用されます。
次のコードは、C++ のマネージ拡張を使用した例です。
__gc class A
{
public:
A() : _n(1)
{
}
protected:
int _n;
};
__gc class B : public A
{
public:
B() : _m(_n)
{
}
private:
int _m;
};
前述したコンストラクターの初期化処理順序に従うと、クラス B のインスタンスを新たに作成すると、次の順で初期化が実行されます。
基本クラス A のコンストラクターが呼び出されます。 _n メンバーが 1 に初期化されます。
B クラスの初期化リストが評価されます。 _m メンバーが 1 に初期化されます。
B クラスのコード本体が実行されます。
同じコードを Visual C++ の新しい構文で記述すると次のようになります。
ref class A
{
public:
A() : _n(1)
{
}
protected:
int _n;
};
ref class B : A
{
public:
B() : _m(_n)
{
}
private:
int _m;
};
新しい構文で B クラスのインスタンスを新たに作成した場合の実行順序は次のとおりです。
B クラスの初期化リストが評価されます。 _m メンバーが 0 に初期化されます (_m クラス メンバーの未初期化の値は 0)。
基本クラス A のコンストラクターが呼び出されます。 _n メンバーが 1 に初期化されます。
B クラスのコード本体が実行されます。
以上のコード例から、同様の構文でも、結果が異なることがわかります。 B クラスのコンストラクターは、基本クラスの値に依存しています。つまり、基本クラス A の値を使ってそのメンバーを初期化します。 しかし、その段階では、A クラスのコンストラクターは呼び出されていません。 この依存関係が特に問題となるのは、メモリまたはリソースの割り当てが基本クラスのコンストラクターで実行されることを前提に継承クラスが設計されている場合です。
C++ マネージ拡張から Visual C++ 2010 に移行する際の影響
継承クラスの動作が基本クラスに影響することはないので、ほとんどの場合、クラス コンストラクターの実行順序をプログラマが意識する必要はありません。 ただし、これらのコード例が示すように、継承クラスの初期化リストが基本クラスのメンバーの値に依存している場合、継承クラスのコンストラクターが大きな影響を受けます。 C++ のマネージ拡張のコードを新しい構文に移行する場合は、このような初期化コードを、(最後に実行されることが保証される) クラス コンストラクターの本体に移動できないか検討してください。