由static_cast和dynamic_cast到C++对象占用内存的全面分析

2020-01-06 16:38:13王振洲

static_cast和dynamic_cast是C++的类型转换操作符。编译器隐式执行的任何类型转换都可以由static_cast显式完成,即父类和子类之间也可以利用static_cast进行转换。而dynamic_cast只能用于类之间的转换。那么dynamic_cast的存在还有什么意义呢?因为dynamic_cast提供了一个重要的特性:运行时类型检查来保证转换的安全性。

用static_cast转换存在的危险

我们知道,一个基类指针不需要进行明确的转换操作,就可以指向基类对象或者派生类对象。比如:


class Base{
  //…
};
class Derived{
  //…
};
int main{
  Base *p = new Base();//OK
  Base *p = new Derived();//OK
}

上面的两种定义都是正确的,那么如果想反过来,让一个子类指针指向父类对象呢?如下代码:


class Base{
  //…
};
class Derived{
  //…
};
int main{
  Derived *p = new Base();//error
  Derived *p = static_cast<Derived*>(new Base());//OK
}

如果直接把Base类型的指针转换为Derived类型的指针,那么编译时会报错。如果在转换时加上static操作符则可以顺利通过编译。但是这种做法是十分危险的,在运行期时可能会出现一些难以预测和查找的错误。如下面代码:


class Base{
  public:
    Base():m_b(4){};
    int m_b;
void m_funcB(){cout << "base" << endl;};
};
class Derived:public Base{
  public:
    Derived():m_d(3){};
    int m_d;
    void m_funcD(){cout << "derived" << endl;};
};
int main(){
  Derived* p = static_cast<Derived*>(new Base());
  cout << p->m_d << endl;
  p->m_funcD();
}


虽然p是Derived类型的指针,但是实际却指向了Base对象,而Base对象不存在m_d这个数据成员,因此输出的结果不可预测(在我的机子上一直输出0)。正是这种不可预测才导致难以追踪的错误,试想,如果执行这段代码会崩溃,那么还是比较还排查的,但是现在并不一定崩溃,只是执行的结果和我们的预测不一致,可能将导致连环的逻辑错误,这就像给自己挖了一个坑或者定时炸弹。

但是很奇怪的一点是,执行p->m_funcD()这一句后,居然可以打印出”derived”。这是怎么回事?m_funcD明明是类Derived的函数,而且类Base里并不存在这个函数,这个我们留在后面说明。

利用dynamic_cast保证转换的安全

原则上,我们不应该让子类指针指向父类的对象。但是如果写下了上面这样的代码,我们希望可以有一种检查机制可以帮助我们发现这个问题,这样就可以避免对转换后的指针进行操作,造成不可预料的后果。

C++是支持运行期类型识别的(RTTI),这种机制除了帮助我们实现多态,还能在类型转换时进行安全检查。回到上面的代码,我们稍作修改: