博客搬家,原地址:https://langzi989.github.io/2016/12/21/条款27-尽量少做转型动作/

C++规则设计的目标之一是,保证类型错误决不可能发生。理论上如果你的程序很干净的通过编译,就表示它并不企图在任何对象身上执行任何不安全,无意义,愚蠢荒谬的操作。这是一个及其具有价值的保证,不要轻易放弃它。
但是在很多种情况下,我们不得不进行转型操作,转型操作破坏了类型系统。这可能会导致任何可能种类的麻烦,有些容易辨识,但是有些可能会很隐晦。所以在需要进行转型操作的时候一定要慎重,尽量通过设计避免不必要的转型操作。

类型转换的形式

首先我们回顾一下类型转换的语法,因为通常有三种不同的形式,可写出相同的类型转换动作。

  • C风格类型转换: (T)expression //将expression转换为类型T
  • 函数式风格类型转换: T(expression) //同上
    上面的两种形式并无差别,纯粹只是把小括号摆放的位置不同而已,我们称上述两种转为为"旧式转型"(old style cast)。

C++还提供四中新式转型(new style):

  • const_cast< T >(expression)

  • dynamic_cast< T >(expression)

  • reinterpret_cast< T >(expression)

  • static_cast< T >(expression)

  • const_cast通常被用来将对象的常量性移除(cast away the constness)。它也是唯一有此能力的C+±style转型操作符。

  • dynamic_cast主要用来执行类型向下转型(safe downcasting),也就是用来决定某对象是否归属继承体系中的某个类型。它是唯一无法由旧式语法执行的动作,也是唯一一个可能耗费重大运行成本的转型动作

  • reinterpret_cast 意图执行低级转型,实际动作及结果可能取决于编译器。这也就表示它不可移植,例如将一个pointer to int 转型为int。这一类型转换在低级代码以外很少见。

  • static_cast 用来强迫隐式转换(implicit conversion),例如将non-const对象转换为const对象,或者将int转换为double等等。也可以用来执行上述多种转换的反向转换,例如将void指针转换为type指针,将pointer to derive 转化为point to bas。但是无法将const转换为non-const。

dynamic_cast与static_cast详解

static_cast是用来强迫隐式类型转换,它可以用于1.基本数据类型以及指针之间的转换;2.类层次中基类与子类成员函数指针的转换;3.类层次结构中基类与子类指针或者引用之间的转换。
dynamic_cast可以用于1.继承关系中类指针或者引用之间的转换;2.包含虚函数之间对象指针的转换3.以及保证转换的安全性。

static_cast

用于基本数据类型转换和指针之间的转换
char a;
int b = static_cast<int>(a);
char c = static_cast<char>(b);
char *pa = NULL;
int *pb = (int*)pa;
pb = static_cast<int*>(pa); //编译错误static_cast只能用于void指针和type指针之间的转换
void *pv = static_cast<void*>(pa); //正确
pb = static_cast<int*>(pv);
类层次中基类与子类成员函数指针的转换
class base {
public:
  base(int t_data) : m_data(t_data) {}
  void printData() { std::cout << m_data << std::endl; }

private:
  int m_data;
};

class child : public base {
public:
  child(int t_data) : base(t_data) {}
  void printData() { std::cout << "this is in the child" << std::endl; }
};
typedef void (base::*basefun)();

int main() {
  base a(10);
  basefun func = &base::printData;
  func = static_cast<basefun>(&child::printData);
  (a.*func)();  //this is in the child
}
类层次结构中基类与子类指针或者引用之间的转换

上行转换:子类指针或引用转换为基类的指针或引用 —安全
下行转换:基类的指针或者引用转换为子类的指针或引用 —危险(避免这样做)

class A
{
};
class B:public A
{
};
class C:public A
{
};
class D
{
};

A objA;
B objB;
A* pObjA = new A();
B* pObjB = new B();
C* pObjC = new C();
D* pObjD = new D();

objA = static_cast<A&>(objB);     //转换为基类引用    
objA = static_cast<A>(objB);
objB = static_cast<B>(objA);      //error 不能进行转换  

pObjA = pObjB;                    //right 基类指针指向子类对象
//objB = objA;                      //error 子类指针指向基类对象
pObjA = static_cast<A*>(pObjB);   //right 基类指针指向子类
pObjB = static_cast<B*>(pObjA);   //强制转换 OK 基类到子类
//pObjC = static_cast<C*>(pObjB);   //error 继承于统一类的派生指针之间转换
//pObjD = static_cast<D*>(pObjC);   //error 两个无关联之间转换

dynamic_cast

继承关系的类指针对象或者引用之间的转换

若积累中没有虚函数,使用dynamic_cast可以将子类的指针或引用转换为基类的指针或引用,与static_cast用法相同,不同的是,这个时候使用dynamic_cast将基类指针转换为子类指针的时候会出现编译错误(static_cast不会,但是很危险)。

class A
{
};
class B:public A
{
};
class C:public A
{
};
class D
{
};

A objA;
B objB;
A* pObjA = new A();
B* pObjB = new B();
C* pObjC = new C();
D* pObjD = new D();
//objA = dynamic_cast<A>(objB);         //error 非引用

objA = dynamic_cast<A&>(objB);
//objB = dynamic_cast<B&>(objA);      //error A 不是多态类型不能转换 若有虚函数则可以进行转换

pObjA = dynamic_cast<A*>(pObjB);
//pObjB = dynamic_cast<B*>(pObjA);    //error A 继承关系 不是多态类型不能转换
//pObjB = dynamic_cast<B*>(pObjC);    //error C 兄弟关系 不是多态类型不能转换
//pObjB = dynamic_cast<B*>(pObjD);    //error D 没有关系 不是多态类型不能转换
包含有虚函数之间的对象指针的转换

使用dynamic_cast将基类指针转换为子类指针的时候并不是永远有效:只有基类指针本身指向的就是一个派生类对象的时候有效。其他时候结果为NULL;

class A
{
Public:
     Virtual ~A(){}
};
class B:public A
{
};
class C:public A
{
};
class D
{
Public:
Virtual ~D(){}
};
pObjB = dynamic_cast<B*>(pObjA);    // worning 继承关系 父类具有虚函数 多态
pObjB = dynamic_cast<B*>(pObjD);    //worning 没有关系 D是多态类型可以转换
//以上结果:pObjB == NULL 此处会发生一个运行时错误
dynamic_cast转换的安全性

当涉及到基类和派生类对象之间的转换的时候,总使用dynamic_cast会避免很多错误,它是安全的,但是它会给程序运行带来巨大的开销。
当子类指针转换为基类指针的时候,两种转型都OK,dynamic_cast开销较大。
当基类指针转换为派生类指针的时候,若基类中没有虚函数,static_cast不会报错,但是做法很危险,dynamic_cast编译不通过。当含有虚函数的时候,若基类指针没有指向派生类,这个时候会返回NULL,所以也是安全的。

虚函数对于dynamic_cast转换的作用

为什么dynamic_cast转换类指针的时候需要虚函数呢?
dynamic_cast转换是在运行时进行转换,运行时转换就需要知道类对象的信息(继承关系等)。
在运行时或者这个信息的是虚函数表指针,通过这个指针可以获取到该类对象的所有的虚函数,包括父类的。因为派生类会继承基类的虚函数表,所以通过这个虚函数表,我们就可以知道类对象的父类,在转换的时候就可以用来判断对象有无继承关系。
所以虚函数对于正确的基类指针转换为子类指针是非常重要的。

effective的三点建议

  • 如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast。如果有个设计需要转型动作,试着发展无需转型的替代设计。
  • 如果转型是必要的,试着将它隐藏与某个函数的背后。客户随后可以调用该函数,而不需要将转型放到他们自己的代码中。
  • 宁可使用C+±style转型,不要使用旧式转型。前者很容易辨识出来,而且也比较有着分门别类的职掌
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐