C++ dynamic_cast操作符

在 C++ 中,编译期的类型转换有可能会在运行时出现错误,特别是涉及到类对象的指针或引用操作时,更容易产生错误。Dynamic_cast 操作符则可以在运行期对可能产生问题的类型转换进行测试。

【例 1】
#include<iostream>
using namespace std;

class base
{
public :
    void m(){cout<<"m"<<endl;}
};

class derived : public base
{
public:
    void f(){cout<<"f"<<endl;}
};

int main()
{
    derived * p;
    p = new base;
    p = static_cast<derived *>(new base);
    p->m();
    p->f();
    return 0;
}
本例中,base 类和 derived 类构成继承关系,base 类中定义了 m() 函数,derived 类中定义了 f() 函数。

在前面介绍多态时,我们一直是用基类指针指向派生类或基类对象,而本例则不同。本例主函数中定义的是一个派生类指针,当我们将其指向一个基类对象时,这是错误的,会导致编译错误。但是通过强制类型转换,我们可以将派生类指针指向一个基类对象,p = static_cast<derived *>(new base);语句实现的就是这样一个功能。

注意,强制类型转换虽然合乎 C++ 语法规定,但是会带来一定的危险。程序中的 p 是一个派生类对象,我们将其强制指向一个基类对象,通过 p 指针调用 m() 函数,因为基类中包含有 m() 函数,这一句没有问题,但通过 p 指针调用 f() 函数,由于 p 指针指向的是基类的对象,而基类中并没有声明 f() 函数,虽然p->f();这一语句虽然没有语法错误,但它却产生了一个运行时的错误。换言之,p 指针是派生类指针,这表明程序设计人员可以通过 p 指针调用派生类的成员函数 f(),但是在实际的程序设计过程中却误将 p 指针指向了一个基类对象,就导致了一个运行期错误。

产生这种运行期的错误原因,在于 static_cast 强制类型转换时并不具有保证类型安全的功能,而 C++ 提供的 dynamic_cast 却能解决这一问题,它可以在程序运行时检测类型转换是否类型安全。当然,dynamic_cast 使用起来也是有条件的,它要求所转换的操作数必须包含多态类类型(即至少包含一个虚函数的类)。

【例 2】
#include<iostream>
using namespace std;

class base
{
public :
    void m(){cout<<"m"<<endl;}
};

class derived : public base
{
public:
    void f(){cout<<"f"<<endl;}
};

int main()
{
    derived * p;
    p = new base;
    p = dynamic_cast<derived *>(new base);
    p->m();
    p->f();
    return 0;
}
本例中利用 dynamic_cast 进行强制类型转换,但是因为 base 类中并不存在虚函数,因此p = dynamic_cast<derived *>(new base);语句会编译失败。dynamic_cast 能否正确转换与目标类型是否为多态类类型无关,dynamic_cast 要求被转换的类型必须为多态类类型。为了解决本例中的语法错误,我们可以将 base 类中的函数 m() 声明为虚函数,也就是virtual void m(){cout<<"m"<<endl;}

dynamic_cast 还要求<>内部所描述的目标类型必须为指针或引用。如例 3 所示,如果我们将例 2 中的主函数换成例 3 的形式,这也是无法通过编译的。

【例 3】
int main()
{
    base b;
    dynamic_cast<derived>(b);
    return 0;
}

我们来看一下正确使用 dynamic_cast 的代码。

【例 4】
#include<iostream>
using namespace std;

class base
{
public :
    virtual void m(){cout<<"m"<<endl;}
};

class derived : public base
{
public:
    void f(){cout<<"f"<<endl;}
};


int main()
{
    derived * p;
    p = dynamic_cast<derived *>(new base);
    if(p)
    {
        p->m();
        p->f();       
    }
    else
        cout<<"Convert not safe!"<<endl;
    return 0;
}
本例中,我们通过 dynamic_cast 来初始化指针 p。初始化过程中,dynamic_cast 会检测操作数 new base 转换为目标类型 derived * 是否能保证类型安全,如果类型安全则将 new base 结果赋给 p 指针,否则返回 0,也即 false。由于本例中是要用基类对象地址去初始化派生类指针,显然是无法保证类型安全的,因此 p 最后得到的返回值是 0。在主函数中经过判断语句,最终程序输出“Convert not safe!”。

Dynamic_cast 转换有自己的规则,下面将通过示例来介绍转换规则。

【例 5】
#include<iostream>
using namespace std;

class base
{
public :
    virtual void m(){cout<<"m"<<endl;}
};

class derived : public base
{
public:
    virtual void f(){cout<<"f"<<endl;}
};

int main()
{
    derived * d;
    d = dynamic_cast<derived *>(new base);
    if(d)
    {
        cout<<"Base to Derived is ok"<<endl;
        delete d;
    }
    else
        cout<<"Base to Derived is error"<<endl;
    base * b;
    b = dynamic_cast<base *>(new derived);
    if(b)
    {
        cout<<"Derived to Base is ok"<<endl;
        delete b;
    }
    else
        cout<<"Derived to Base is error"<<endl;

    return 0;
}
本例中,derived 类继承自 base 类。为了测试 dynamic_cast 转换规则,我们在两个类中各自定义了一个虚函数。主函数中分别测试基类转换为派生类和派生类转换为基类时 dynamic_cast 转换的返回值。本例最终运行结果如下:

Base to Derived is error
Derived to Base is ok


从结果可以看出,不能将指向基类对象的指针转换为指向派生类对象的指针,但是可以将指向派生类对象的指针转换为指向基类对象的指针。

【例 6】
#include<iostream>
using namespace std;

class A
{
public :
    virtual void m(){cout<<"m"<<endl;}
};

class B
{
public:
    virtual void f(){cout<<"f"<<endl;}
};

int main()
{
    A * a;
    a = dynamic_cast<A *>(new B);
    if(a)
    {
        cout<<"B to A is ok"<<endl;
        delete a;
    }
    else
        cout<<"B to A is error"<<endl;
    B * b;
    b = dynamic_cast<B *>(new A);
    if(b)
    {
        cout<<"A to B is ok"<<endl;
        delete b;
    }
    else
        cout<<"A to B is error"<<endl;

    return 0;
}
本例中定义了两个类 A 和 B,这两个类不构成继承关系,我们尝试将指向两个类对象的指针进行互相转换,看程序运行结果:

B to A is error
A to B is error

从程序运行结果不难看出,任意两个不相关的多态类类型之间的转换也是不能进行的。

总结

dynamic_cast 的转换规则是:只允许将指向派生类对象的指针转换为指向基类对象的指针。

C++提供的两个类型转换操作符 static_cast 和 dynamic_cast,static_cast 可以用于任何类型的强制类型转换,但是它不保证转换过程中的类型安全,dynamic_cast 只能用于多态类类型的转换,而且要求转换的目的类型必须为指针或引用,并且它可以保证转换过程中类型安全。