C++学习之显式类型转换与运行时类型识别RTTI

static_cast

const_cast

reinterpret_cast

运行时类型识别(RTTI)

dynamic_cast

哪种情况下dynamic_cast和static_cast使用的情况一样?

什么情况下使用dynamic_cast代替虚函数?

typeid

命名的强制类型转换形式如下:

cast_name<type>(expression);

其中:cast_name指static_cast、dynamic_cast、const_cast、reinterpret_cast中的一种;

          type指要转换的目标类型;

          expression指要转换的值或表达式。

static_cast:任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast。同时,对于编译器无法自动执行的类型转换也非常有用。static_cast不能转换掉expression的const、volitale、或者__unaligned属性。没有运行时类型检查来保证转换的安全性。

它主要有如下几种用法: 

①用于类层次结构中基类和子类之间指针或引用的转换。 

    进行上行转换(把子类的指针或引用转换成基类表示)是安全的; 

    进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。 

②用于基本数据类型之间的转换。如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。 

③把空指针转换成目标类型的空指针。

④把任何类型的表达式转换成void类型。

如:

【代码1】

1
2
3
double *d;
void* p = &d; //正确,任何"非常量对象"的地址都能存入void*
double* dp = static_cast<double*>(p);//正确,将void*转换为初始指针类型

const_cast:只改变运算对象的底层const性质,不改变表达式(运算对象)的类型。可用于增加/去除运算对象的const属性。同时,只能使用const_cast来进行更改const属性,其他任何形式的命名强制类型转换都会引起编译器错误。如:

【代码2】

1
2
3
4
5
const char *pc;
char *p = const_cast<char*>(pc);//正确:但通过p写值是未定义的行为
char *q = static_cast<char*>(pc);//错误:static_cast不能转换const性质
static_cast<string>(pc);//正确:字符串字面值转换为string类型
const_cast<string>(pc); //错误:const_cast只用来改变常量属性

const_cast常常用于函数重载的上下文中。如:

【代码3】

1
2
3
4
5
6
7
8
9
10
11
//比较两个string对象的长度,返回较短的那个引用
const string &shorterString(const string &s1, const string &s2){
    return s1.size()<=s2.size() ? s1 : s2;
}
 
string & shorterString(string &s1, string &s2){
    //调用const版本,先将s1和s2转换为const版本,返回的引用 r 为const版本
    auto &r = shorterString(const_cast<const string&>(s1),
                            const_cast<const string&>(s2));
    return const_cast<string&>(r);  //再次使用const_cast消除掉r的const属性,得到一个普通的引用
}

reinterpret_cast:C++ Primer中解释:通常为运算对象的位模式提供较低层次上的重新解释。不懂~~有如下例子:

【代码4】

1
2
int *ip;
char *pc = reinterpret_cast<char*>(ip);

在代码4中,必须牢记pc实际上指向的是一个int而不是字符,如果把pc当成普通的字符指针使用就可能在运行时发生错误。如:

1
string str(pc); //错误!

谨记:使用reinterpret_cast是非常危险的!要想安全的使用,必须对涉及的类型和编译器实现转换的过程都非常了解。所以,还是尽量不要使用的好!

同时,所有的强制类型转换,能不使用的情况尽量不要使用,因为其干扰了正常的类型检查。

运行时类型识别(RTTI):(由两个运算符实现)

        typeid运算符,用来返回表达式的类型;

        dynamic_cast运算符,用于将基类的指针或引用安全的转换成派生类的指针或引用。

这两个运算符特别适用于以下情况:使用‘基类对象’的‘指针或引用’执行某个‘派生类操作’并且’该操作‘不是‘虚函数’。

dynamic_cast运算符的使用形式:

1
2
3
4
//以下形式中:type必须为一个类类型,且通常该类型应该含有虚函数
dynamic_cast<type *>(e);  //e必须是一个有效的指针;转换失败时结果为0
dynamic_cast<type &>(e);  //e必须为左值;转换失败时抛出bad_cast异常
dynamic_cast<type &&>(e); //e不能为左值;转换失败时抛出bad_cast异常

其中,e的类型必须满足以下三个条件中的任一个:

  1. e的类型是目标type的公有派生类;

  2. e的类型是目标type的公有基类;

  3. e的类型是目标type的类型。

如果不满足上述三个中的任一个条件,则转换失败。同时,当使用dynamic_cast对一个空指针执行转换时,结果是所需类型的空指针。

dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。  

在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。如:

【代码5】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class A{
public:
    A();
    ~A();
    virtual void fun1();
    void fun2();
};
class B:public A{
    B();
    ~B();
    virtual void fun3();
    void fun4();
};
void f1(const A &a){ //引用类型的dynamic_cast
    try{
        const B &b = dynamic_cast<const B &>(a);
        //使用a引用的B对象
    }catch(std::bad_cast){
        //处理类型转换失败的情况
    }
}
   
void f2(){//指针类型的dynamic_cast
    A *ap = new A();//使用基类对象的【指针】
    if(B *bp = dynamic_cast<B *>(ap)){//动态转换ap为指向B类型的指针;成功则继续,不成功则为0
        bp->fun4();     //执行某个‘派生类操作’并且’该操作‘不是‘虚函数’
        //其他操作
    }else{
        //使用ap指向的A对象
    }
    delete ap;  ap=NULL;
}

如果对无继承关系或者没有虚函数的对象指针进行转换、基本类型指针转换以及基类指针转换为派生类指针,都不能通过编译。

哪种情况下dynamic_cast和static_cast使用的情况一样?

【代码6】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class C{
    //类内定义
};
class D:public A, public C{ //多重继承
    //类内定义
};
  
void f3(){ //单继承情况下
    //如果ap指向的即为B的对象,则使用dynamic_cast和static_cast效果一样
    A *ap = new B();
    B *bp = dynamic_cast<B *>(ap);  //A要有虚函数,否则使用dynamic_cast会产生编译错误,
    B *bp1 = static_cast<B *>(ap);  //static_cast则没有这个限制
    delete ap; delete bp; delete bp1;
    ap=bp=bp1=NULL;  //防止野指针
    //如果ap指向的不为B的对象,则用dynamic_cast返回NULL,能够更早的禁止error的发生;
    //如果用static_cast返回的不为NULL,但是运行时可能抛出异常
    A *ap = new A();
    B *bp = dynamic_cast<B *>(ap); //正确,但bp指向的为NULL
    B *bp1 = static_cast<B *>(ap); //错误,bp1在运行时可能会抛出异常
    delete ap; delete bp; delete bp1;
    ap=bp=bp1=NULL;  //防止野指针
}
void f4(){  //多继承情况下
    //如果ap指向的即为C的对象,则使用dynamic_cast和static_cast效果一样,都可以转换为C的指针
    A *ap = new B();
    C *cp = dynamic_cast<C *>(ap);
    C *cp1 = static_cast<C *>(ap);
     
    //若要将ap转换为dp,则都可以,只是dynamic_cast可以一次转换,static_cast需要一个过渡;dp的转换相当于cp1和dp1,相当于dp2
    D *dp = dynamic_cast<D *>(ap);
    D *dp1 = static_cast<D *>(cp1);
    D *dp2 = static_cast<D *>(static_cast<C *>(ap));
    delete ap; delete cp; delete cp1; delete dp; delete dp1; delete dp2;
    ap=cp=cp1=dp=dp1=dp2=NULL;  //防止野指针
    //如果ap指向的不为C的对象,则用dynamic_cast返回NULL,能够更早的禁止error的发生;情况与f3中一样!
}

dynamic_cast可用于进行交叉转换:

【代码7】

1
2
3
4
5
6
7
8
9
10
class E:public A{
    //类内定义
};
void f5(){  //交叉转换
    //使用static_cast进行转换是不被允许的,将在编译时出错;而使用 dynamic_cast的转换则是允许的,结果是空指针。
    B *bp = new B();
    E *ep = dynamic_cast<E *>(bp);//正确,返回空指针
    E *ep1 = static_cast<E *>(bp);//错误,编译时出错
    //其他内容
}

dynamic_cast的讨论: 

在探究 dynamic_cast 的设计意图之前,值得留意的是很多 dynamic_cast 的实现都相当慢。 例如,至少有一种通用的实现部分地基于对类名字进行字符串比较。如果你在一个位于四层深的单继承体系中的对象上执行 dynamic_cast,在这样一个实现下的每一个 dynamic_cast 都要付出相当于四次调用 strcmp 来比较类名字的成本。对于一个更深的或使用了多继承的继承体系,付出的代价会更加昂贵。 

对 dynamic_cast 的需要通常发生在这种情况下:你要在一个你确信为派生类的对象上执行派生类的操作,但是你只能通过一个基类的指针或引用来操控这个对象。 有两个一般的方法可以避免这个问题: 

  1. 使用存储着直接指向派生类对象的指针的容器,从而消除通过基类接口操控这个对象的需要。当然,这个方法不允许你在同一个容器中存储所有可能的基类的派生类的指针。为了与不同的窗口类型一起工作,你可能需要多个类型安全(type-safe)的容器。

  2.  通过一个基类的接口操控所有可能的 Window 派生类,就是在基类中提供一个让你做你想做的事情的虚函数。例如,尽管只有 SpecialWindows 能 blink,在基类中声明这个函数,并提供一个什么都不做的缺省实现或许是有意义的。 

所以:避免强制转型的随意应用,特别是在性能敏感的代码中应用 dynamic_casts,如果一个设计需要强制转型,设法开发一个没有强制转型的侯选方案。 如果必须要强制转型,设法将它隐藏在一个函数中。客户可以用调用那个函数来代替在他们自己的代码中加入强制转型。

什么情况下使用dynamic_cast代替虚函数?

当基类代码不可知,需要在派生类里面新增新成员函数,但是又无法取得基类的源代码,不能在基类里面通过加虚函数接口调用新成员函数,可以通过dynamic_cast强制转换来获得调用。如:

【代码8】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class A{
public:
    virtual void fun(){
        cout<<"A::fun"<<endl;
    }
//其他代码未知,不能更改A的代码
};
class B:public A{
public:
    void fun(){
        cout<<"B::fun"<<endl;
    }
    void get(){
        cout<<"B::get"<<endl;
    }
};
  
int main()
{
    A *ap = new B;
    ap->fun ();
    (dynamic_cast<B*>(ap))->get (); //此时使用dynamic_cast获取调用
    if(B *o=dynamic_cast<B *>(ap))//此时使用dynamic_cast获取调用
        o->get();
    cin.get();
    return 0;
}
 
//输出为:
B::fun
B::get

typeid运算符允许向表达式提问:“你的对象是什么类型?”

typeid(e),其中e可以是任意表达式或类型的名字。操作结果是一个常量对象的引用。如果表达式为一个引用,则typeid返回该引用所引用对象的类型。如果e为数组或函数,则不会执行向指针的标准类型转换,如数组,会返回数组类型。当运算对象不属于类类型或者是一个不包含任何虚函数的类时,typeid运算符返回的是e的静态类型。当e是定义了至少一个虚函数的类的左值时,typeid的结果直到运行时才能求得。

当typeid作用于指针p时,返回的是该指针的静态编译时类型;当作用于指针所指的对象(*p)时,要在运行时才能求得返回类型。同时,如果指针p所指的对象的类型不含有虚函数,则p可以为一个无效的指针。否则,指针所指的对象(*p)将在运行时求值,此时p必须为一个有效的指针,如p此时为一个空指针,则typeid(*p)会抛出std::bad_typeid异常。