转型语法,通常有三种不同的形式。
C风格的转型动作看起来像这样:
(T)expression
函数风格的转型动作看起来像这样:
T(expression)
C++还提供四种转型:
const_cast<T>(expression)
dynamic_cast<T>(expression)
reinterpret_cast<T>(expression)
static_cast<T>(expression)
const_cast<T>(expression)
通常用来将对象的常量性移除.
dynamic_cast<T>(expression)
主要用来执行安全向下转型,也就是用来决定某个对象是否归属继承体系中的某个类型.
reinterpret_cast<T>(expression)
意图执行低级转型,比如将一个int*
转换成一个int
.
static_cast<T>(expression)
用来强迫隐式转换,比如将no-const对象转换为const对象,将int转为double等等.
旧式转型仍然合法,但新式转型比较受欢迎.原因是:
1.它们很容易在代码中被辨识出来
2.转型动作的目标越窄化,编译器越可能诊断出错误的运用。
比如:如果你打算将常量性去掉,除非使用const-cast否则无法通过编译.
旧式转型使用的时机通常在explicit构造函数中。比如:
class Person
{
public:
explicit Person(int age);
}
很多人认为,转型其实什么都没做,只是告诉编译器把某种类型视为另一种类型.这是错误的观念.任何一个类型转换往往真的令编译器编译出运行期间执行的代码.
比如:
int x, y;
double d = static_cast<double>(x) / y;
将int x转型为double肯定会产生一些代码。这或许不会让你惊讶,但下面的例子就很不同了:
class Base{...};
class Derived: public Base{...};
Derived d;
Base * pb = &d;
这里只不过是建立一个基类指针指向派生类对象,但有时上诉的两个指针值并不相同。这种情况下会有偏移量在运行期被施行于派生智障身上,用来获取正确的基类指针值.
也就是说,单一对象比如Derived d
可能拥有一个以上的地址,当base*
指向它的地址和Derived*
指向它的地址。
注意:即使你知道对象如何布局,然后设计了一个行的同的转型,可能在某一个平台可以,在其他平台并不一定行得通,不要理所当然的认为。
很多应用框架都要求派生类内的虚函数代码的第一个动作就先调用基类对应的函数,比如:
class window
{
public:
virtual void OnResize(){...}
...
};
class SpecialWindow:public Window
{
public:
virtual void OnResize()
{
static_cast<Window>(*this).onResize();
}
};
在SpecialWindow
中进行了转型,为了调用Window
的onResize()
.
如你预料的意义,这段程序将*this
转型为Window
,但恐怕你没想到的是,这个函数它调用的并不是当前对象上的函数,而是转型动作建立的一个*this对象之基类成分的暂时副本的onResize.
也就是说,在一个副本上调用了Window::onResize
,然后在当前对象上执行SpecialWindow专属动作。如果Window::onResize
修改了对象内容,当前对象其实也没被改动,改动的是副本,然而如果SpecialWindow::onResize
内如果也修改了对象,当前对象真的会被改动,也就导致了没有改动完全:基类没有改动,而派生类却改动成功。
解决办法就是:直接调用Window的onResize()
virtual void OnResize()
{
Window::onResize();
...
}
总结:
1.尽量避免转型,特别是在注重效率的代码中避免dynamic-cast.
如果有个设计需要转型动作,试着发展不需要转型的替代设计
2.如果转型是必要的,试着将它隐藏于某个函数背后,客户随后可以调用该函数,而不需将转型放进它们的代码内
3.宁可使用C++风格的转型,也不要使用旧式转型。