質問: obj2 // ERROR!! ``` オブジェクトの方は分かるのですが、ポインタの方はなぜできないのかわかりません。 また、ダウンキャストができる条件を教えてください。 環境は、Linux g++です。
回答: こんにちは。 > > こんなコードがあったとします。 > > > 「int型のポインタにchar型のポインタは代入できないエラー」になる筈です。 > > この場合、ptrは、int型のポインタと解釈されるのでしょうか?それとも、char型のポインタでしょうか? > > > `int * ptr = reinterpret_cast(&qtr);`とすれば代入できます。 つまり、ptrはint型へのポインタなのです。 > > こちらは、どうでしょうか?よく見かけるコードです。ptrは、結局のところどちらのポインタだと解釈されるんでしょうか?? > > > こちらはちょっと悩ましいですよね。C++は普通に記述す限り、型に厳密です。`Base *ptr = &obj;`のptrはBase型へのポインタとして宣言していますので、何を代入しようがBase型へのポインタです。 なのである意味安心して使えます。 ただし、指している先はDerived型のオブジェクトです。指している先を仮にBase型として取り扱っても未定義動作が発生する危険はありません。 > > 上記のコードの例だと、ptrに、Derived型のオブジェクトが入っていることは、実行しなくてもわかりますよね?? > > > 確かにそのコードであれば分かりますが、それは「特別なケース」なのです。分かる場合もありますが、一般的には分からないと考えるのが正しいです。 ``` bool make_derived; std::cin >> make_derived; Base* ptr=(make_derived)?new Derived:new Base; ``` のような場合、実行時にしか分かりません。 また、例示されている関数 f() をfoo.cppで定義し、bar.cppから呼び出したとします。 foo.cppをコンパイルしている時のコンパイラは、bar.cppの情報を全く持っていませんので、コンパイル時にはどんなパラメータで呼び出されているか分かりません。 foo.cppが何かのライブラリとして配布されていて、それを入手した人がbar.cppから呼び出すような場合にありがちな話です。 ご提示されているコードは、Base型オブジェクトが渡されると危険なコードです。 そのような時は dynamic_cast<>を使い、結果がnullptrになっていないことを確認することで安全になります。(なお、nullポインタを`*`で参照へ変換すると未定義動作を引き起こしますので、ポインタで処理する必要があります。) dynamic_cast<>は意外なほど遅いので使わないに越したことはないですが、使わないで処理するのは結構面倒なので、速度を要求されない場所なら使うのも有りと思います。(ダウンキャストそのものを否定する人もいますが、使った方が良い時も結構あります。全ての派生クラス用の仮想関数を定義するとか、できれば避けたいものです。) > > 3つ目にダウンキャストはなぜ危険なのでしょうか?? > > > 以下のソースを実行すると何が表示されるでしょうか? つまり未定義動作を引き起こしますので、危険なのです。 ``` #include struct Base { int x; }; struct Derived : public Base { int y; }; int main() { Base b; b.x=0; Derived* d_ptr = static_cast(&b); std::cout << d_ptr->y; } ``` > > 基本クラスのオブジェクトを派生クラスのオブジェクトに代入することは可能です。 > > しかし、逆に、派生クラスのオブジェクトを基本クラスのオブジェクトに代入することはできません。 > > これは、派生クラスには、基本クラスにないメンバ変数を持っている可能性があるから、コピーのしようがない・・・ということでしょう。 > > > えっ! 完全に逆ですよ。 派生クラスは基底クラスへ代入できます。そして、スライシングが発生して泣くことがあります。 しかし、逆はコンパイラが禁止しています。派生クラスにだけある変数に値を設定できないからだと思います。 なお、基底クラスへのconst参照を取る代入演算子やコンストラクタを定義すれば通ります。定義した人の責任で設定すれば良いからです。