C++语言学习(十九)——C++类型识别

一、C++类型识别简介

1、C++类型识别简介

C++是静态类型语言,其数据类型是在编译期就确定的,不能在运行时更改。 C++语言中,静态类型是对象自身的类型,动态类型是指针(引用)所指向对象的实际类型。 RTTI(Run-Time Type Information)即运行时类型识别,C++通过RTTI实现对多态的支持。 为了支持RTTI,C++提供了一个type_info类和typeid与dynamic_cast两个关键字。

2、type_info结构体

type_info :   存储特点类型的相关信息,常用来比较对象类型,type_info类的具体内容由编译器实现来决定。其声明如下:

class type_info {
public:
    virtual ~type_info();
    bool operator== (const type_info& rhs) const;
    bool operator!= (const type_info& rhs) const;
    bool before (const type_info& rhs) const;
    const char* name() const;
private:
    type_info (const type_info& rhs);
    type_info& operator= (const type_info& rhs);
};

type_info的构造函数和赋值操作符为私有,因此,程序中创建type_info对象的唯一方法是使用typeid操作符。C++标准只是告诉编译器需要实现type_info::name函数,但不同的编译器实现各不相同,因此typeid(int).name()不同编译器编译运行后输出不一样。

3、typeid关键字

typeid:   typeid语法规则如下:typeid(expr);   typeid表达式返回type_info类型,expr可以是各种类型名,对象和内置基本数据类型的实例、指针或者引用。当作用于指针和引用时,将返回实际指向对象的类型信息。   如果表达式的类型是类类型且至少包含有一个虚函数,则typeid操作符返回表达式的动态类型,需要在运行时确定;否则,typeid操作符返回表达式的静态类型,在编译时就可以确定。   当把typeid作用于指针的解引用*p时,若指针p为0,则:如果p指向的类型是带虚函数的类类型,则typeid(*p)在运行时抛出一个bad_typeid异常;否则,typeid(*p)的结果与p的值是不相关的,在编译时就可以确定。

4、dynamic_cast关键字

dynamic_cast:   动态类型转换,运行时类型安全检查。dynamic_cast会检查待转换的源对象是否真的可以转换成目标类型,这种检查不是语法上的,而是真实情况的。许多编译器都是通过vtable找到对象的RTTI信息的,如果基类没有虚方法,也就无法判断一个基类指针变量所指对象的真实类型。   dynamic_cast将一个指向基类的指针转换为一个指向派生类的指针,如果不能正确转换,则返回空指针。 C++语言提供了typeid关键字用于获取类型信息,typeid关键字返回对应参数的类型信息。typeid返回一个type_info类对象,当typeid的参数为NULL时将抛出异常。typeid的参数既可以时类型也可以是变量,当参数为类型,返回静态类型信息;当参数为变量,如果不存在虚函数表,返回静态类型信息,如果存在虚函数表,返回动态类型信息。 typeid操作符的返回结果是名为type_info的标准库类型的对象的引用。 typeid在不同C++编译器实现是不同的。

RTTI(Run-Time Type Identification,运行时类型识别)

二、C++类型转换

C++类型转换分为向上类型转换和向下类型转换。

1、向上类型转换

C++语言中,向上类型转换描述的是子类向基类的强制类型转换,是一种隐式类型转换。在向上类型转换过程中,覆盖方法和子类对象数据丢失的现象称为切割。

#include <iostream>

using namespace std;

class Base
{
public:
    Base(int value = 0)
    {
        data = value;
    }
    virtual void print()
    {
        cout << "Base::print data = " << data << endl;
    }
protected:
    int data;
};

class Derived : public Base
{
public:
    Derived(int value = 0)
    {
        data = value;
    }
    virtual void print()
    {
        cout << "Derived print data = " << data << endl;
    }
protected:
    int data;
};

int main(int argc, char *argv[])
{
    Derived d(100);
    //将子类向上转型为基类
    Base b = d;//直接赋值,产生切割
    b.print();//Base::print data = 0

    Base& rb = d;//引用赋值,不产生切割
    rb.print();//Derived print data = 100

    Base* pb = &d;//指针赋值,不产生切割
    pb->print();//Derived print data = 100

    //Derived* dp = pb;//error,不允许隐式向下转型
    return 0;
}

在向上强制转换过程中,使用指针和引用不会造成切割,而使用直接赋值会造成切割。

2、向下类型转换

C++语言中,向下类型转换描述的是基类向子类的强制类型转换,使用dynamic_cast进行向下强制类型转换。dynamic_cast会在运行时进行类型检查。如果向下转型是安全的(如果基类指针或者引用实际指向一个派生类的对象),dynamic_cast会返回类型转换后的指针。如果向下转型不安全(即基类指针或者引用没有指向一个派生类的对象),dynamic_cast返回空指针。 使用dynamic_cast时,类中必须定义虚函数。

#include <iostream>

using namespace std;

class Base
{
public:
    Base(int value = 0)
    {
        data = value;
    }
    virtual void print()
    {
        cout << "Base::print data = " << data << endl;
    }
protected:
    int data;
};

class Derived : public Base
{
public:
    Derived(int value = 0)
    {
        data = value;
    }
    virtual void print()
    {
        cout << "Derived print data = " << data << endl;
    }
protected:
    int data;
};

int main(int argc, char *argv[])
{
    //指针
    Base* bp1 = new Base(101);
    Derived* dp11 = static_cast<Derived*>(bp1);
    cout << "Base" << endl;
    cout << bp1 << endl;
    cout << dp11<< endl;
    dp11->print();//Base::print data = 101
    Derived* dp12 = dynamic_cast<Derived*>(bp1);
    cout << dp12 << endl;//0,向下转型失败
    if(dp12 != NULL)
    {
        dp12->print();
    }

    Base* bp2 = new Derived(102);
    Derived* dp21 = static_cast<Derived*>(bp2);
    cout << "Derived" << endl;
    cout << bp2 << endl;
    cout << dp21<< endl;
    dp21->print();//Derived print data = 102
    Derived* dp22 = dynamic_cast<Derived*>(bp2);
    cout << dp22 << endl;//向下转型成功
    if(dp22 != NULL)
    {
        dp22->print();//Derived print data = 102
    }
    //引用
    Base b1(10);
    Derived& rd11 = static_cast<Derived&>(b1);
    rd11.print();//Base::print data = 10
    //Derived& rd12 = dynamic_cast<Derived&>(b1);//exception
    Derived b2(10);
    Derived& rd21 = static_cast<Derived&>(b2);
    rd21.print();//Derived print data = 10
    Derived& rd22 = dynamic_cast<Derived&>(b2);
    rd22.print();//Derived print data = 10

    return 0;
}

上述代码中,如果指针、引用实际指向的对象为派生类对象,使用static_cast、dynamic_cast转换都是安全的;如果指针、引用实际指向的对象为基类对象,使用dynamic_cast会返回NULL指针或抛出异常,使用static_cast关键字返回执行基类对象的指针或引用,不能访问派生类的覆盖方法与成员。

3、多继承时的向下转型

#include <iostream>

using namespace std;

class BaseA
{
public:
    BaseA(int value = 0)
    {
        data = value;
    }
    virtual void printA()
    {
        cout << "BaseA::print data = " << data << endl;
    }
protected:
    int data;
};

class BaseB
{
public:
    BaseB(int value = 0)
    {
        data = value;
    }
    virtual void printB()
    {
        cout << "BaseB::print data = " << data << endl;
    }
protected:
    int data;
};

class Derived : public BaseA, public BaseB
{
public:
    Derived(int value = 0)
    {
        data = value;
    }
    virtual void printA()
    {
        cout << "Derived printA data = " << data << endl;
    }
    virtual void printB()
    {
        cout << "Derived printB data = " << data << endl;
    }
protected:
    int data;
};

int main(int argc, char *argv[])
{
    //BaseA
    cout << "BaseA" << endl;
    BaseA* bpa = new BaseA(10);
    cout << bpa << endl;
    Derived* pd1 = static_cast<Derived*>(bpa);
    cout << pd1 << endl;
    pd1->printA();//BaseB::print data 10
    //pd1->printB();//exception,实际指向BaseA对象,没有printB方法
    Derived* pd2 = dynamic_cast<Derived*>(bpa);
    cout << pd2 << endl;//0,向下转型失败
    if(pd2 != NULL)
    {
        pd2->printA();
        pd2->printB();
    }

    //BaseB
    cout << "BaseB" << endl;
    BaseB* bpb = new BaseB(10);
    cout << bpb << endl;
    //pd3指向bpb前8字节的地址
    Derived* pd3 = static_cast<Derived*>(bpb);
    cout << pd3 << endl;
    //pd3->printA();//exception
    //pd3->printB();//exception
    Derived* pd4 = dynamic_cast<Derived*>(bpb);
    cout << pd4 << endl;//0,向下转型失败
    if(pd4 != NULL)
    {
        pd4->printA();
        pd4->printB();
    }

    cout << "Derived" << endl;
    BaseA* bpd = new Derived(101);
    cout << bpd << endl;
    Derived* pd5 = static_cast<Derived*>(bpd);
    cout << pd5 << endl;
    pd5->printA();//Derived printA data = 101
    pd5->printB();//Derived printB data = 101
    Derived* pd6 = dynamic_cast<Derived*>(bpd);
    cout << pd6 << endl;
    if(pd6 != NULL)
    {
        pd6->printA();//Derived printA data = 101
        pd6->printB();//Derived printB data = 101
    }
    BaseA* pa = static_cast<BaseA*>(bpd);
    pa->printA();
    //BaseB* pb = static_cast<BaseB*>(bpd);//error,
    BaseB* pb = dynamic_cast<BaseB*>(bpd);//正确,
    pb->printB();

    cout << "Derived+" << endl;
    Derived* dpd = new Derived(102);
    cout << dpd << endl;
    BaseA* dpa = static_cast<BaseA*>(dpd);
    cout << dpa << endl;
    dpa->printA();
    BaseB* dpb1 = static_cast<BaseB*>(dpd);//
    cout << dpb1 << endl;
    dpb1->printB();
    BaseB* dpb2 = dynamic_cast<BaseB*>(dpd);//
    cout << dpb2 << endl;
    dpb2->printB();

    return 0;
}

上述代码中,bpa指针指向BaseA对象,使用static_cast关键字对bpa进行向下转型为Derived指针对象时,返回bpa的值,由于实际指向BaseA对象,因此对BaseB方法时会导致异常;使用dynamic_cast关键字对bpa进行向下转型时,转型失败,返回NULL。 bpb指针实际指向BaseB对象,使用static_cast关键字对bpb进行向下转型为Derived指针对象时,返回bpb地址的-8字节的地址,该地值是一个不合法的Derived对象地址,因此对该地址调用BaseA、BaseB类的方法时会导致异常;使用dynamic_cast关键字对bpa进行向下转型时,转型失败,返回NULL。 bpd指针实际指向Derived对象,使用static_cast关键字对bpd进行向下转型为Derived指针对象时,返回bpd的值,可以合法调用BaseA、BaseB类的方法;使用dynamic_cast关键字对bpd进行向下转型时,返回bpd的值,可以合法调用BaseA、BaseB类的方法。如果使用static_cast关键字将BaseA类型指针bpd转型为BaseB指针时,C++编译器报错;必须使用dynamic_cast关键字,dynamic_cast会在运行时对指针进行调整。 Derived类型的dpd指针指向Derived对象,使用static_cast关键字和dynamic_cast关键字都可以进行向上转型。

三、C++内省机制

所谓内省是指面向对象语言的一种在运行期间查询对象信息的能力, 比如如果语言具有运行期间检查对象型别的能力,那么语言是型别内省(type intropection)的,型别内省可以用来实施多态。 C++的内省比较有限,仅支持型别内省, C++的型别内省是通过运行时类型识别(RTTI)(Run-Time Type Information)中的typeid 以及dynamic_case关键字来实现的。