dynamic_cast 关键字
发布时间:2023-06-12 13:22:58 所属栏目:语言 来源:
导读:dynamic_cast 用于在类的继承层次之间进行类型转换,它既允许向上转型(Upcasting),也允许向下转型(Downcasting)。向上转型是无条件的,不会进行任何检测,所以都能成功;向下转型的前提必须是安全的,要借助 RT
|
dynamic_cast 用于在类的继承层次之间进行类型转换,它既允许向上转型(Upcasting),也允许向下转型(Downcasting)。向上转型是无条件的,不会进行任何检测,所以都能成功;向下转型的前提必须是安全的,要借助 RTTI 进行检测,所有只有一部分能成功。 dynamic_cast 与 static_cast 是相对的,dynamic_cast 是“动态转换”的意思,static_cast 是“静态转换”的意思。dynamic_cast 会在程序运行期间借助 RTTI 进行类型转换,这就要求基类必须包含虚函数;static_cast 在编译期间完成类型转换,能够更加及时地发现错误。 dynamic_cast 的语法格式为: dynamic_cast <newType> (expression) newType 和 expression 必须同时是指针类型或者引用类型。换句话说,dynamic_cast 只能转换指针类型和引用类型,其它类型(int、double、数组、类、结构体等)都不行。 对于指针,如果转换失败将返回 NULL;对于引用,如果转换失败将抛出std::bad_cast异常。 1) 向上转型(Upcasting) 向上转型时,只要待转换的两个类型之间存在继承关系,并且基类包含了虚函数(这些信息在编译期间就能确定),就一定能转换成功。因为向上转型始终是安全的,所以 dynamic_cast 不会进行任何运行期间的检查,这个时候的 dynamic_cast 和 static_cast 就没有什么区别了。 「向上转型时不执行运行期检测」虽然提高了效率,但也留下了安全隐患,请看下面的代码: #include <iostream> #include <iomanip> using namespace std; class Base{ public: Base(int a = 0): m_a(a){ } int get_a() const{ return m_a; } virtual void func() const { } protected: int m_a; }; class Derived: public Base{ public: Derived(int a = 0, int b = 0): Base(a), m_b(b){ } int get_b() const { return m_b; } private: int m_b; }; int main(){ //情况① Derived *pd1 = new Derived(35, 78); Base *pb1 = dynamic_cast<Derived*>(pd1); cout<<"pd1 = "<<pd1<<", pb1 = "<<pb1<<endl; cout<<pb1->get_a()<<endl; pb1->func(); //情况② int n = 100; Derived *pd2 = reinterpret_cast<Derived*>(&n); Base *pb2 = dynamic_cast<Base*>(pd2); cout<<"pd2 = "<<pd2<<", pb2 = "<<pb2<<endl; cout<<pb2->get_a()<<endl; //输出一个垃圾值 pb2->func(); //内存错误 return 0; } 情况①是正确的,没有任何问题。对于情况②,pd 指向的是整型变量 n,并没有指向一个 Derived 类的对象,在使用 dynamic_cast 进行类型转换时也没有检查这一点,而是将 pd 的值直接赋给了 pb(这里并不需要调整偏移量),最终导致 pb 也指向了 n。因为 pb 指向的不是一个对象,所以get_a()得不到 m_a 的值(实际上得到的是一个垃圾值),pb2->func()也得不到 func() 函数的正确地址。 pb2->func()得不到 func() 的正确地址的原因在于,pb2 指向的是一个假的“对象”,它没有虚函数表,也没有虚函数表指针,而 func() 是虚函数,必须到虚函数表中才能找到它的地址。 2) 向下转型(Downcasting) 向下转型是有风险的,dynamic_cast 会借助 RTTI 信息进行检测,确定安全的才能转换成功,否则就转换失败。那么,哪些向下转型是安全地呢,哪些又是不安全的呢?下面我们通过一个例子来演示: #include <iostream> using namespace std; class A{ public: virtual void func() const { cout<<"Class A"<<endl; } private: int m_a; }; class B: public A{ public: virtual void func() const { cout<<"Class B"<<endl; } private: int m_b; }; class C: public B{ public: virtual void func() const { cout<<"Class C"<<endl; } private: int m_c; }; class D: public C{ public: virtual void func() const { cout<<"Class D"<<endl; } private: int m_d; }; int main(){ A *pa = new A(); B *pb; C *pc; //情况① pb = dynamic_cast<B*>(pa); //向下转型失败 if(pb == NULL){ cout<<"Downcasting failed: A* to B*"<<endl; }else{ cout<<"Downcasting successfully: A* to B*"<<endl; pb -> func(); } pc = dynamic_cast<C*>(pa); //向下转型失败 if(pc == NULL){ cout<<"Downcasting failed: A* to C*"<<endl; }else{ cout<<"Downcasting successfully: A* to C*"<<endl; pc -> func(); } cout<<"-------------------------"<<endl; //情况② pa = new D(); //向上转型都是允许的 pb = dynamic_cast<B*>(pa); //向下转型成功 if(pb == NULL){ cout<<"Downcasting failed: A* to B*"<<endl; }else{ cout<<"Downcasting successfully: A* to B*"<<endl; pb -> func(); } pc = dynamic_cast<C*>(pa); //向下转型成功 if(pc == NULL){ cout<<"Downcasting failed: A* to C*"<<endl; }else{ cout<<"Downcasting successfully: A* to C*"<<endl; pc -> func(); } return 0; } 运行结果: Downcasting failed: A* to B* Downcasting failed: A* to C* ------------------------- Downcasting successfully: A* to B* Class D Downcasting successfully: A* to C* Class D 这段代码中类的继承顺序为:A --> B --> C --> D。pa 是A*类型的指针,当 pa 指向 A 类型的对象时,向下转型失败,pa 不能转换为B*或C*类型。当 pa 指向 D 类型的对象 当使用 dynamic_cast 对指针进行类型转换时,程序会先找到该指针指向的对象,再根据对象找到当前类(指针指向的对象所属的类)的类型信息,并从此节点开始沿着继承链向上遍历,如果找到了要转化的目标类型,那么说明这种转换是安全的,就能够转换成功,如果没有找到要转换的目标类型,那么说明这种转换存在较大的风险,就不能转换。 pa 指向 A 类对象,根据该对象找到的就是 A 的类型信息,当程序从这个节点开始向上遍历时,发现 A 的上方没有要转换的 B 类型或 C 类型(实际上 A 的上方没有任何类型了),所以就转换败了。对于情况②,pa 指向 D 类对象,根据该对象找到的就是 D 的类型信息,程序从这个节点向上遍历的过程中,发现了 C 类型和 B 类型,所以就转换成功了。 从表面上看起来 dynamic_cast 确实能够向下转型,本例也很好地证明了这一点:B 和 C 都是 A 的派生类,我们成功地将 pa 从 A 类型指针转换成了 B 和 C 类型指针。但是从本质上讲,dynamic_cast 还是只允许向上转型,因为它只会向上遍历继承链。造成这种假象的根本原因在于,派生类对象可以用任何一个基类的指针指向它,这样做始终是安全的。本例中的情况②,pa 指向的对象是 D 类型的,pa、pb、pc 都是 D 的基类的指针,所以它们都可以指向 D 类型的对象,dynamic_cast 只是让不同的基类指针指向同一个派生类对象罢了。 (编辑:汽车网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
