C++标准转换运算符dynamic_cast

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  ider.cs@gmail.com
///////////////////////////////////////////////////////////////////////////
#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_cast 都是成功的,但是正确性方面,我对两者的结果都先进行了是否非空的判别:dynamic_cast的结果显示是空指针,而static_cast则是非空 指针。但很显然,static_cast的结果应该算是错误的,子类指针实际所指的是基类的对象,而基类对象并不具有子类的Study()方法(除非妈妈 又想去接受个"继续教育")。

对于没有关系的两个类之间的转换,输出结果表明,dynamic_cast依然是返回一个空指针以表示转换是不成立的;static_cast直接在编译期就拒绝了这种转换。

reinterpret_cast成功进行了转换,而且返回的值并不是空指针,但是结果显然是错误的,因为Children类显然不具有 Stranger的Self_Introduce()。虽然两者都具有name数据成员和Speak()方法,,Speak()方法也只是调用了该相同名 称的成员而已,但是对于Speak()的调用直接造成了程序的崩溃。

其实前面static_cast的转换的结果也会跟reinterpret_cast一样造成的程序的崩溃,只是类的方法都只有一份,只有数据成员属于对象, 所以在调用那些不会访问对象的数据的方法时(如Stranger的Self_Introduce())并不会造成崩溃。而 daughter_s->Speak();和daughter_s->Study();调用了数据成员却没有出现运行错误,则是因为该成员是 从基类继承下来的,通过地址偏移可以正确的到达数据成员所在的地址以读取出数据。

最后,程序里还用dynamic_cast希望把用其他转换运算符转换过去的指针转换回来。 对于使用static_cast转换后指向了子类对象的基类指针,dynamic_cast判定转换是合理有效的,因此转换成功获得一个非空的指针并且正 确输出了结果;而对于reinterpret_cast转换的类型,的确如它的功能一样——重新解析,变成新的类型,所以才得到dynamic_cast 判定该类型已经不是原来的类型结果,转换得到了一个空指针。

总得说来,static_cast和reinterpret_cast运算符要么直接被 编译器拒绝进行转换,要么就一定会得到相应的目标类型的值。 而dynamic_cast却会进行判别,确定源指针所指的内容,是否真的合适被目标指针接受。如果是否定的,那么dynamic_cast则会返回 null。这是通过检查"运行期类型信息"(Runtime type information,RTTI)来判定的,它还受到编译器的影响,有些编译器需要设置开启才能让程序正确运行(导师的PPT详细介绍了Visual Studio的情况),因此dynamic_cast也就不能用传统的转换方式来实现了。

虚函数(virtual function)对dynamic_cast的作用

已经在前面反复提到过面向对象的多态性,但是这个多态性到底要如何体现呢?dynamic_cast真的允许任意对象指针之间进行转换,只是最后返回个null值来告知转换无结果吗?

实际上,这一切都是虚函数(virtual function)在起作用。

在C++的面对对象思想中,虚函数起到了很关键的作用,当一个类中拥有至少一个虚函数,那么编译器就会构建出一个虚函数表(virtual method table)来指示这些函数的地址,假如继承该类的子类定义并实现了一个同名并具有同样函数签名(function siguature)的方法重写了基类中的方法,那么虚函数表会将该函数指向新的地址。此时多态性就体现出来了:当我们将基类的指针或引用指向子类的对象的时候,调用方法时,就会顺着虚函数表找到对应子类的方法而非基类的方法。

当然虚函数表的存在对于效率上会有一定的影响,首先构建虚函数表需要时间,根据虚函数表寻到到函数也需要时间。

因为这个原因如果没有继承的需要,一般不必在类中定义虚函数。但是对于继承来说,虚函数就变得很重要了,这不仅仅是实现多态性的一个重要标志,同时也是dynamic_cast转换能够进行的前提条件。

假如去掉上个例子中Stranger类析构函数前的virtual,那么语句

Children* child_r = dynamic_cast<Children*> (stranger_r);

在编译期就会直接报出错误,具体原因不是很清楚,我猜测可能是因为当类没有虚函数表的时候,dynamic_cast就不能用RTTI来确定类的具体类型,于是就直接不通过编译。

这不仅仅是没有继承关系的类之间的情况,如果基类或者子类没有任何虚函数(如果基类有虚函数表,子类当然是自动继承了该表),当他们作为dynamic_cast的源类型进行转换时,编译也会失败。

这种情况是有可能存在的,因为在设计的时候,我们可能不需要让子类重写任何基类的方法。但实际上,这是不合理的。导师在讲解多态性的时候,时刻强调了一点:如果要用继承,那么一定要让析构函数是虚函数;如果一个函数是虚函数,那么在子类中也要是虚函数。

我会将导师关于"为何继承中析构函数必须是虚函数"的讲解总结一下,当然你也可以看这边文章来了解原因。

Director: Jim Fawcett

    1. C++ Language Tutorial - Type Casting
    2. Object Oriented Design
    3. IBM Complilers - XL C/C++ V9.0 for Linux - The dynamic_cast operator (C++ only)
    4. MSDN Visual C++ Develope Center - dynamic_cast Operator
    5. In C++, what’s a virtual destructor and when is it needed?
    6. Wikipedia The Free Encyclopedia - Run-Time Type Information
    7. Wikipedia The Free Encyclopedia - Virtual Function
时间: 2024-11-15 00:33:55

C++标准转换运算符dynamic_cast的相关文章

C# 标准查询运算符的集合接口详解教程

支持标准查询运算符的集合接口 System.Linq.Enumeralbe类提供的一些常用的API 来执行集合处理1.匿名类型2.隐匿类型的局部变量3.集合初始化器4.集合5.标准查询运算符 本章主要讨论泛型集合接口.非泛型的集合类,待查. 一.匿名类型和隐式类型的局部变量声明 C#3.0增强.1.匿名类型一种特殊的数据类型,它最终是由编译器声明的,而非通过已定义好的类来声明的.和匿名函数相似,当编译器看到一个匿名类型时,会自动执行一些后台操作,生成必要的代码,允许像显式声明的那样使用它. cl

C++运算符重载转换运算符

为什么需要转换运算符? 大家知道对于内置类型的数据我们可以通过强制转换符的使用来转换数据,例如(int)2.1f;自定义类也是类型,那么自定义类的对象在很多情况下也需要支持此操作,C++提供了转换运算符重载函数,它使得自定义类对象的强转换成为可能. 转换运算符的生命方式比较特别,方法如下: operator 类名(); 转换运算符的重载函数是没有返回类型的,它和类的构造函数,析构函数一样是不遵循函数有返回类型的规定的,他们都没有返回值. 下面我看一个例子,看看它是如何工作的: //例1 //程序

C++类型转换归纳总结_C 语言

学过C++的人都知道,C++是强类型语言,因此变量在使用前就要声明数据类型,不同数据类型分配的内存空间大小也是不同,在转换类型时尤其需要注意这个问题,以防止数据丢失或越界溢出.本文将详细归纳总结一下C++的类型转换. C++从C发展而来,也继承两种C风格的转换:隐式转换和显式转换. 1.隐式转换 隐式转换是指由编译系统自动进行,不需要人工干预的类型转换,例如: short a = 2000; int b; b = a; 隐式转换,也包括构造函数和运算符的转换,例如: class A {}; cl

C++之static_cast, dynamic_cast, const_cast

转自:http://www.cnblogs.com/chio/archive/2007/07/18/822389.html 首先回顾一下C++类型转换: C++类型转换分为:隐式类型转换和显式类型转换 第1部分. 隐式类型转换 又称为"标准转换",包括以下几种情况:1) 算术转换(Arithmetic conversion) : 在混合类型的算术表达式中, 最宽的数据类型成为目标转换类型.   int ival = 3;double dval = 3.14159; ival + dva

深入解析C++中的动态类型转换与静态类型转换运算符_C 语言

dynamic_cast 运算符将操作数 expression 转换成类型为type-id 的对象. 语法 dynamic_cast < type-id > ( expression ) 备注 type-id 必须是一个指针或引用到以前已定义的类类型的引用或"指向 void 的指针".如果 type-id 是指针,则expression 的类型必须是指针,如果 type-id 是引用,则为左值. 有关静态和动态强制转换之间区别的描述,以及各在什么情况下适合使用,请参见 st

static_cast, dynamic_cast, const_cast探讨

 首先回顾一下C++类型转换: C++类型转换分为:隐式类型转换和显式类型转换 第1部分. 隐式类型转换 何时发生隐式类型转换 在下面这些情况下,编译器会自动地转换运算对象的类型: 在大多数表达式中,比int类型小的整型值首先提升为较大的整数类型 在条件中,非布尔值转换为布尔类型 初始化过程中,初始值转换成变量的类型:在赋值语句中,右侧运算对象转换成左侧运算对象的类型 如果算术运算或关系运算的运算对象有多种类型,需要转换成同一种类型 函数调用时也会发生类型转换   又称为"标准转换",

注释符、运算符与通配符

4.5.1 注释符(Annotation)在Transact-SQL 中可使用两类注释符.ANSI 标准的注释符"--" 用于单行注释:与C语言相同的程序注释符号,即"/**/"."/*"用于注释文字的开头,"*/"用于注释文字的结尾,可在程序中标识多行文字为注释. 4.5.2 运算符(Operator)    1 算术运算符包括:+(加).―(减).(乘).(除).%(取余)    2 比较运算符包括:>(大于)<

MS SQL基础教程:注释符、运算符与通配符

4.5.1 注释符(Annotation) 在Transact-SQL 中可使用两类注释符. ANSI 标准的注释符"--" 用于单行注释: 与C语言相同的程序注释符号,即"/**/"."/*"用于注释文字的开头,"*/"用于注释文字的结尾,可在程序中标识多行文字为注释. 4.5.2 运算符(Operator) 1 算术运算符 包括:+(加).―(减).(乘).(除).%(取余) 2 比较运算符 包括:>(大于)<

NHibernate3.0剖析:Query篇之NHibernate.Linq标准查询

系列引入 NHibernate3.0剖析系列分别从Configuration篇.Mapping篇.Query篇.Session策略篇.应用篇等方面全面揭示NHibernate3.0新特性和应用及其各种应用程序的集成,基于NHibernte3.0版本.如果你还不熟悉NHibernate,可以快速阅读NHibernate之旅系列文章导航系列入门,如果你已经在用NHibernate了,那么请跟上NHibernate3.0剖析系列吧. NHibernate专题:http://kb.cnblogs.com