dynamic_cast
20054 ワード
作者整理ありがとうございます
dynamic_cast <new_type> (expression)
dynamic_cast演算子は、コンパイラのプロパティ設定に関連し、オブジェクト向けの多様性がプログラム実行時の状態にも関係するため、従来の変換方式を完全に使用することはできません.しかし、そのため最もよく使われ、最も不可欠な演算子です.
static_castと同様にdynamic_castの変換には、ターゲットタイプとソースオブジェクトにも一定の関係が必要です.継承関係です.もっと正確に言えばdynamic_castは、両者に継承関係があるかどうかを確認するために使用されます.したがって、演算子は実際にはクラスオブジェクトベースのポインタと参照のクラス変換のみを受け入れます.この点ではdynamic_castはまたreinterpret_castと一致しているが、実際には大きな違いがある.
やはりコードで説明して、コンパイラに説明させましょう.
/////////////////////////////////////////////////////////////////////////////
// cast_operator_comparison.cpp
// Language: C++
// Complier: Visual Studio 2010, Xcode3.2.6
// Platform: MacBook Pro 2010
// Application: none
// Author: Ider, Syracuse University [email protected]
///////////////////////////////////////////////////////////////////////////
#include <string>
#include <iostream>
using namespace std;
class Parents
{
public:
Parents(string n="Parent"){ name = n;}
virtual ~Parents(){}
virtual void Speak()
{
cout << "\tI am " << name << ", I love my children." << endl;
}
void Work()
{
cout << "\tI am " << name <<", I need to work for my family." << endl;;
}
protected:
string name;
};
class Children : public Parents
{
public:
Children(string n="Child"):Parents(n){ }
virtual ~Children(){}
virtual void Speak()
{
cout << "\tI am " << name << ", I love my parents." << endl;
}
/* **Children inherit Work() method from parents, **it could be treated like part-time job. */
void Study()
{
cout << "\tI am " << name << ", I need to study for future." << endl;;
}
private:
//string name; //Inherit "name" member from Parents
};
class Stranger
{
public:
Stranger(string n="stranger"){name = n;}
virtual ~Stranger(){}
void Self_Introduce()
{
cout << "\tI am a stranger" << endl;
}
void Speak()
{
//cout << "I am a stranger" << endl;
cout << "\tDo not talk to "<< name << ", who is a stranger." << endl;
}
private:
string name;
};
int main() {
/******* cast from child class to base class *******/
cout << "dynamic_cast from child class to base class:" << endl;
Children * daughter_d = new Children("Daughter who pretend to be my mother");
Parents * mother_d = dynamic_cast<Parents*> (daughter_d); //right, cast with polymorphism
mother_d->Speak();
mother_d->Work();
//mother_d->Study(); //Error, no such method
cout << "static_cast from child class to base class:" << endl;
Children * son_s = new Children("Son who pretend to be my father");
Parents * father_s = static_cast<Parents*> (son_s); //right, cast with polymorphism
father_s->Speak();
father_s->Work();
//father_s->Study(); //Error, no such method
cout << endl;
/******* cast from base class to child class *******/
cout << "dynamic_cast from base class to child class:" << endl;
Parents * father_d = new Parents("Father who pretend to be a my son");
Children * son_d = dynamic_cast<Children*> (father_d); //no error, but not safe
if (son_d)
{
son_d->Speak();
son_d->Study();
}
else cout << "\t[null]" << endl;
cout << "static_cast from base class to child class:" << endl;
Parents * mother_s = new Parents("Mother who pretend to be a my daugher");
Children * daughter_s = static_cast<Children*> (mother_s); //no error, but not safe
if (daughter_s)
{
daughter_s->Speak();
daughter_s->Study();
}
else cout << "\t[null]" << endl;
cout << endl;
/******* cast between non-related class *******/
cout << "dynamic_cast to non-related class:" << endl;
Stranger* stranger_d = dynamic_cast<Stranger*> (daughter_d);
if (stranger_d)
{
stranger_d->Self_Introduce();
stranger_d->Speak();
}
else cout <<"\t[null]"<<endl;
//Stranger* stranger_s = static_cast<Stranger*> (son_s); //Error, invalid cast
cout << "reinterpret_cast to non-related class:" << endl;
Stranger* stranger_r = reinterpret_cast<Stranger*> (son_s);
if (stranger_r)
{
stranger_d->Self_Introduce();
//stranger_d->Speak(); //This line would cause program crush,
//as "name" could not be found corretly.
}
else cout << "\t[null]" << endl;
cout << endl;
/******* cast back*******/
cout << "use dynamic_cast to cast back from static_cast:" << endl;
Children* child_s = dynamic_cast<Children*> (father_s);
if (child_s)
{
child_s->Speak();
child_s->Work();
}
else cout << "\t[null]" << endl;
//cout<<typeid(stranger_r).name()<<endl;
cout << "use dynamic_cast to cast back from reinterpret_cast:" << endl;
Children* child_r = dynamic_cast<Children*> (stranger_r);
if (child_r)
{
child_r->Speak();
child_r->Work();
}
else cout << "\t[null]" << endl;
delete daughter_d;
delete son_s;
delete father_d;
delete mother_s;
return 0;
}
/********************* Result *********************/
//dynamic_cast from child class to base class:
// I am Daughter who pretend to be my mother, I love my parents.
// I am Daughter who pretend to be my mother, I need to work for my family.
//static_cast from child class to base class:
// I am Son who pretend to be my father, I love my parents.
// I am Son who pretend to be my father, I need to work for my family.
//
//dynamic_cast from base class to child class:
// [null]
//static_cast from base class to child class:
// I am Mother who pretend to be a my daugher, I love my children.
// I am Mother who pretend to be a my daugher, I need to study for future.
//
//dynamic_cast to non-related class:
// [null]
//reinterpret_cast to non-related class:
// I am a stranger
//
//use dynamic_cast to cast back from static_cast:
// I am Son who pretend to be my father, I love my parents.
// I am Son who pretend to be my father, I need to work for my family.
//use dynamic_cast to cast back from reinterpret_cast:
// [null]
上のコードと出力結果から、
サブクラスからベースクラスへのポインタ変換の場合static_castとdynamic_castはいずれも成功し正確である(成功とは変換にコンパイルエラーや実行異常がないことであり、正確とは方法の呼び出しとデータのアクセス出力が所望の結果であることを指す)、これはオブジェクト向けの多態性の完璧な体現である.
ベースクラスからサブクラスへの変換、static_castとdynamic_キャストはすべて成功したが、正確性の面では、両者の結果について空かどうかの判別を先に行った.dynamic_castの結果は空のポインタであり、static_castは空でないポインタです.明らかにstatic_castの結果は誤りであるべきで、サブクラスポインタは実際にはベースクラスのオブジェクトを指しているが、ベースクラスオブジェクトはサブクラスのStudio()メソッドを持っていない(母がまた「継続教育」を受けたくない限り).
関係のない2つのクラス間の変換について、出力結果はdynamic_castは依然として空のポインタを返して変換が成立しないことを示す.static_castはコンパイル期間中にこの変換を直接拒否した.
reinterpret_キャストは変換に成功し、返された値は空のポインタではありませんが、ChildrenクラスにStrangerのSelf_がないことは明らかです.Introduce().どちらもnameデータメンバーとSpeak()メソッドを持ち,Speak()メソッドも同じ名前のメンバーを呼び出しただけであるが,Speak()の呼び出しに対して直接プログラムのクラッシュをもたらした.
実は前のstatic_キャストの変換の結果もreinterpret_とcastと同様に発生するプログラムのクラッシュは、クラスのメソッドが1つしかなく、データメンバーのみがオブジェクトに属するため、オブジェクトにアクセスしないデータのメソッド(StrangerのSelf_Introduce()など)を呼び出すとクラッシュは発生しません.そしてdaughter_s->Speak();とdaughter_s->Study();データ・メンバーが呼び出されても実行エラーが発生しないのは、そのメンバーがベース・クラスから継承されているためであり、アドレス・オフセットによってデータ・メンバーが存在するアドレスに正しく到達してデータを読み出すことができるからである.
最後に、プログラムにはdynamic_が使われています.castは、他の変換演算子で変換したポインタを変換したい.static_を使用する場合cast変換後にサブクラスオブジェクトを指すベースクラスポインタdynamic_castは変換が合理的で有効であると判定し、変換は空でないポインタを得ることに成功し、結果を正しく出力した.reinterpret_についてはキャスト変換のタイプは、確かにその機能のように、再解析して新しいタイプになるのでdynamic_castは,このタイプが既に元のタイプではないと判定し,変換して空のポインタを得た.
要するにstatic_castとreinterpret_cast演算子は、コンパイラによって直接変換を拒否されるか、対応するターゲットタイプの値が必ず得られます.そしてdynamic_castは,ソースポインタが指す内容が,本当にターゲットポインタに受け入れられるか否かを判別する.否定的であればdynamic_castはnullを返します.これは「運転期間タイプ情報」(Runtime type information,RTTI)をチェックすることによって判定され、またコンパイラの影響を受け、一部のコンパイラはプログラムを正しく動作させるためにオンに設定する必要がある(指導者のPPTはVisual Studioの場合を詳細に説明している)ためdynamic_castも従来の変換方式では実現できない.
虚関数(virtual function)対dynamic_キャストの役割
オブジェクト向けのマルチステートについては前述のように繰り返していますが、この多態性はいったいどのように体現されているのでしょうか.dynamic_castは本当に任意のオブジェクトポインタ間の変換を許可しますが、最後にnull値を返して変換の結果がないことを通知しますか?
実際、これらはすべて虚関数(virtual function)が働いています.
C++の対象思想において、虚関数は重要な役割を果たし、1つのクラスに少なくとも1つの虚関数がある場合、コンパイラはこれらの関数のアドレスを示す虚関数テーブルを構築し、クラスのサブクラス定義を継承し、同じ関数署名(function siguature)の方法でベースクラスのメソッドを書き換えると、虚関数テーブルはその関数を新しいアドレスに指向する.このとき,多態性は,ベースクラスのポインタや参照をサブクラスのオブジェクトに向けると,メソッドが呼び出されると,ベースクラスではなく虚関数数テーブルに沿って対応するサブクラスのメソッドが見つかることを示している.
もちろん,虚関数テーブルの存在は効率に一定の影響を及ぼすが,まず虚関数テーブルを構築するには時間がかかり,虚関数テーブルに基づいて関数を見つけるにも時間がかかる.
このため、継承の必要がなければ、クラスで虚関数を定義する必要はありません.しかし継承にとって虚関数は重要になり,これは多態性を実現する重要なフラグであるだけでなくdynamic_でもある.cast変換が可能な前提条件.
前の例のStrangerクラス解析関数の前のvirtualを削除すると、文
Children* child_r = dynamic_cast<Children*> (stranger_r);
コンパイル期間中にエラーが直接報告されますが、具体的な原因はよくわかりません.クラスに虚関数テーブルがない場合、dynamic_castはRTTIでクラスの具体的なタイプを決定することができず,コンパイルを直接行わない.これは、継承関係のないクラス間だけでなく、ベースクラスまたはサブクラスに虚関数がない場合(ベースクラスに虚関数テーブルがある場合、サブクラスは当然自動的にテーブルを継承します)、dynamic_castのソースタイプを変換すると、コンパイルも失敗します.
このような状況は、設計時にサブクラスにベースクラスを書き換える方法を必要としない可能性があるため、存在する可能性があります.しかし、実際には、これは合理的ではありません.指導者は多態性を説明する時、常に1点を強調しました:もし継承を使うならば、それでは必ず構造関数を虚関数にしなければなりません;1つの関数が虚関数である場合、サブクラスでも虚関数です.
「なぜ継承中の構造関数が虚関数でなければならないのか」についての指導者の説明をまとめます.もちろん、こちらの文章を見て原因を知ることもできます.
Director:
Jim Fawcett