トップ回答者
親より前に子クラスのコンストラクタを呼ぶには? @ C++/CLI

質問
-
お世話になります。 Visual Studio 2008 .NET3.5 @ Windows7 x64
基本的に、親コンストラクタが子の前に呼ばれるのが道理で、【1】 のように明示的に書くこともできるかと思いますが、例えば 【2】 のような記述をすることで、親コンストラクタが実行される前に 前処理【3】 を行うことはできないのでしょうか? もちろんこれではダメなのですが、何か別の方法で。
実は、親クラス TAAA のコンストラクタで呼ばれる メソッド Func() を、子クラス TBBB でオーバーライドしているのですが、その中で、子クラスで 初期化【3】 の必要な フィールド _CCC を用いている【4】のです。
・public ref class TAAA { protected: virtual void Func() {} public: TAAA() { Func(); } } ・public ref class TBBB : puclic TAAA { protected: virtual void Func() override { __super:: Func(); _CCC->Hoge(); ←【4】 } public: TBBB() : TAAA() ←【1】 { _CCC = gcnew TCCC(); ←【3】 __super:: TAAA() ←【2】 } }
もしくはこのような状況を、トリッキーなことをせず、オブジェクト指向的に上手く整流する方法があるのでしょうか?
もしくは、全てのコンストラクタが呼ばれたあと実行される固有メソッドなどがあれば便利なのですが。
となると、もはや以下のようにするしかないのでしょうか?
・public ref class TAAA { protected: virtual void Func() {} virtual void Create() {} virtual void Init() { Func(); } public: TAAA() { Create(); Init(); } } ・public ref class TBBB : puclic TAAA { protected: virtual void Func() override { __super:: Func(); _CCC->Hoge(); } virtual void Create() { __super:: Create(); _CCC = gcnew TCCC(); } }
何卒ご教示下さい。
回答
-
- C++とC++/CLI共通で気を付けること
- C++のみに適用されること
- C++/CLIのみに適用されること
これらの区別がついていない場合、3つひっくるめて気を付けるべきと思います。ですので一般論として推奨されないと書きました。
推奨されないのですか。ただ CLI は C++ とは逆に 派生>基底 の順で生成されるようなので、一応は実行できてしまうようですね。
違います。totojoさんの挙げた初期化子リストとは別の理由で実行できています。
C++と異なりC++/CLIではメモリ確保はガーベージコレクタに任されており、またGCで確保した時点でその領域の型が決定されています(GetType()で得られるものが確定しているということ)。で、C++の場合、コンストラクター実行中に仮想関数呼び出しのためのポインターのセットアップが行われますが、C++/CLIの場合、GCが確保した時点=コンストラクター実行前にセットアップが行われるため、コンストラクター内でも安全に仮想関数が呼び出せます。
他にもいろいろ方法があるので挙げておきます。
- 基底クラスのコンストラクタ引数として初期化関数の関数ポインターを受け取るようにし、派生クラスは必要な関数を渡す。Effective C++に書かれている方法
- boost::base_from_memberを使う。C++のみ
- 初期化子リストを使って初期化する。C++/CLIのみ
- _CCCをメンバー変数(フィールド)ではなくプロパティとして実装し、get時に初期化する。C++/CLIのみ
一番最後の方法が.NET的な解かもしれません。
- 回答としてマーク luxidea 2011年9月21日 10:53
すべての返信
-
C++/CLI以前の問題として、C++言語でそもそもEffective C++第3版 第2章 9項 「コンストラクタやデストラクタ内では決して仮想関数を呼び出さないようにしよう」です。C++とC++/CLIとの違いを把握していての質問ならわかりますが、一般論としてはC++/CLIであってもFunc()を呼ぶ行為そのものが推奨できません。
そういったことを解決するためにC++の場合はboost::base_from_memberを使ったりもします。
C++/CLIの場合はtotojoさんの書かれた通り初期化子リストを使う気もしますが…ところで_CCCがどこにも宣言されていないのですが派生クラスのメンバー変数なのでしょうか?
-
お返事ありがとうございます。
初期化子リストではダメでしょうか?
なるほど、コンストラクタの後ろ【1】に、さらに初期化コードを列挙できるのですね。勉強になりました。確かに美しいので、挑戦してみたいと思います。ありがとうございました。
・メンバ初期化子リストを使ってメンバの初期化をした - 猫でも分かる( ゜Д゜)<zero_divide→無限
・コンストラクタ
・暇神降臨 初期化子リストの実行順序 -
いつもお世話になります。
C++/CLI以前の問題として、C++言語でそもそもEffective C++第3版 第2章 9項 「コンストラクタやデストラクタ内では決して仮想関数を呼び出さないようにしよう」です。C++とC++/CLIとの違いを把握していての質問ならわかりますが、一般論としてはC++/CLIであってもFunc()を呼ぶ行為そのものが推奨できません。
推奨されないのですか。ただ CLI は C++ とは逆に 派生>基底 の順で生成されるようなので、一応は実行できてしまうようですね。他言語では呼びまくってしまっていたので、誤った思想が...。更正します。
そういったことを解決するためにC++の場合はboost::base_from_memberを使ったりもします。
なるほど、勉強してみます。
・More C++ Idioms/メンバによる基本クラスの初期化(Base-from-Member) - Wikibooks
ところで_CCCがどこにも宣言されていないのですが派生クラスのメンバー変数なのでしょうか?
あっ、すみません。(^_^;) はい、仮に TBBB メンバとしてそういうのがあった場合という話です。書き忘れました。
-
- C++とC++/CLI共通で気を付けること
- C++のみに適用されること
- C++/CLIのみに適用されること
これらの区別がついていない場合、3つひっくるめて気を付けるべきと思います。ですので一般論として推奨されないと書きました。
推奨されないのですか。ただ CLI は C++ とは逆に 派生>基底 の順で生成されるようなので、一応は実行できてしまうようですね。
違います。totojoさんの挙げた初期化子リストとは別の理由で実行できています。
C++と異なりC++/CLIではメモリ確保はガーベージコレクタに任されており、またGCで確保した時点でその領域の型が決定されています(GetType()で得られるものが確定しているということ)。で、C++の場合、コンストラクター実行中に仮想関数呼び出しのためのポインターのセットアップが行われますが、C++/CLIの場合、GCが確保した時点=コンストラクター実行前にセットアップが行われるため、コンストラクター内でも安全に仮想関数が呼び出せます。
他にもいろいろ方法があるので挙げておきます。
- 基底クラスのコンストラクタ引数として初期化関数の関数ポインターを受け取るようにし、派生クラスは必要な関数を渡す。Effective C++に書かれている方法
- boost::base_from_memberを使う。C++のみ
- 初期化子リストを使って初期化する。C++/CLIのみ
- _CCCをメンバー変数(フィールド)ではなくプロパティとして実装し、get時に初期化する。C++/CLIのみ
一番最後の方法が.NET的な解かもしれません。
- 回答としてマーク luxidea 2011年9月21日 10:53
-
ご指摘感謝します。
これらの区別がついていない場合、3つひっくるめて気を付けるべきと思います。ですので一般論として推奨されないと書きました。
あっ、そういう意味でしたか。はい、両者の違いはなるべく意識するようにしてます。まぁ当面 C++/CLI しか使う予定はないですが。
C++/CLIの場合、GCが確保した時点=コンストラクター実行前にセットアップが行われるため、コンストラクター内でも安全に仮想関数が呼び出せます。
なるほど、そういう仕組みだったのですね。ネイティブ言語の Delphi では【2】のような位置ずらしも、コンストラクタでの仮想関数も自由だったので、同じような仕組みなのかと思ってました。ということは、まぁ C++/CLI界 限定の話としては、仮想関数呼び出しもあながち 違法 というわけではないのですね。
・派生クラスのコンストラクタが実行される前に派生クラスを操作する・Delphiのばあい
- 基底クラスのコンストラクタ引数として初期化関数の関数ポインターを受け取るようにし、派生クラスは必要な関数を渡す。Effective C++に書かれている方法
- boost::base_from_memberを使う。C++のみ
- 初期化子リストを使って初期化する。C++/CLIのみ
- _CCCをメンバー変数(フィールド)ではなくプロパティとして実装し、get時に初期化する。C++/CLIのみ
とてもよく分かりました。1 はデリゲートとかで実装できそうですね。子から親に割り込みを入れるというのは、確かに上手いです。4 はそういえば、Control.Handle とかって、そんな実装になっていますよね。アクセスするまで初期化されておらず、初めてアクセスした時点で生成されると。
なるほど、ではこれらを駆使して、設計に挑戦してみたいと思います。ありがとうございました。
- 編集済み luxidea 2011年9月21日 13:10