条款27:尽量少做转型动作
C++的四种显示类型转换
①reinterpret_cast(在编译期间实现转换)
将一个类型的指针转换成另一个类型的指针。这种转换不用于修改指针变量值数据存放的格式(不改变指针变量值),只需在编译期间重新解释指针的类型就可以做到,可以将指针值转换为一个整型数。但是不能用于非指针类型的转换,否则将不会通过编译。
意图执行低级转型,结果取决于编译器,故不可移植。
②const_cast(在编译期间实现转换)
用于去除指针变量的常量属性,将它转换为一个对应指针类型的普通变量,反过来,也可以将一个非常量的指针变量转换为常量指针变量
③stactic_cast(在编译期间实现转换)
用来强迫隐式转换,例如将int转换为double,将void*转换为typed指针,将non-const对象转换为const对象。
④dynamic_cast(在运行期间实现转换,并可以返回转换成功与否的标志)
用于继承体系下的"向下安全转换",通常用于将基类对象指针转换为其子类对象指针,它也是唯一一种无法用旧式转换进行替换的转型,也是唯一可能耗费重大运行成本的转型动作.
运算符dynamic_cast可以针对两种数据类型做强制转换:指针类型和引用类型
转型在对象中运用可能会导致错误的调用
class Window { public: Window(const int size) : _size(size) {} virtual ~Window() {} virtual void onResize(const int size) {_size = size;} int GetSize() const {return _size;} private: int _size; }; class GlassWinsow : public Window { public: GlassWinsow(const int size, const int gSize) : Window(size), _gSize(gSize) {} void onResize(const int size, const int gSize) { static_cast<Window>(*this).onResize(size); _gSize = gSize; } int GetGlassSize() const {return _gSize;} private: int _gSize; };
调用
GlassWinsow gw(2, 3); cout<<"Window size:"<<gw.GetSize()<<" GlassWindow size:"<<gw.GetGlassSize()<<endl; gw.onResize(4, 5); cout<<"Window size:"<<gw.GetSize()<<" GlassWindow size:"<<gw.GetGlassSize()<<endl;
结果
Window size:2 GlassWindow size:3
Window size:2 GlassWindow size:5
将*this转型为window调用的并不是当前对象上的函数,而是稍早转型动作所建立的一个“*this对象的base class成分”的暂时副本身上的onResize!并不是在当前对象身上调用Window::onResize之后又在该对象上执行SpecialWindow专属行为。不,它是在“当前对象之base
calss成分”的副本上调用Window::onResize,然后在当前对象上执行SpecialWindow专属动作。如果Window::onResize修改了对象内容,当前对象其实没被改动,改动的是副本。然而SpecialWindow::onResize内如果也修改对象,当前对象真的会被改动。这使当前对象进入一种“伤残”状态:其base class成分的更改没有落实,而derived
class成分的更改倒是落实了。
解决之道是拿掉转型动作,代之你真正想要说的话。所以,真正的解决方法是:
修改后
void onResize(const int size, const int gSize) { Window::onResize(size); _gSize = gSize; }
同样调用结果
Window size:2 GlassWindow size:3
Window size:4 GlassWindow size:5
dynamic_cast
dynamic_cast的许多实现版本执行速度相当慢。假如至少有一个很普通的实现版本基于“class名称之字符串比较”,如果你在四层深的单继承体系内的某个对象身上执行dynamic_cast,可能会耗用多达四次的strcmp调用,用以比较class名称。深度继承或多重继承的成本更高!某些实现版本这样做有其原因(它们必须支持动态链接)。在对注重效率的代码中更应该对dynamic_cast保持机敏猜疑。
之所以需要用dynamic_cast,通常是因为你想在一个你认定为derived class对象身上执行derived class操作函数,但你的手上只有一个“指向base”的pointer或者reference,你只能靠他们来处理对象。两个一般性做法可以避免这个问题:
①使用容器,并在其中存储直接指向derived class对象的指针(通常是智能指针),如此便消除了“通过base class接口处理对象”的需要。假设先前的Window/GlassWindow 继承体系中有GlassWindow 才支持闪烁效果,试着不要这样做:
在类GlassWindow 中添加闪烁效果函数
void blink() {cout<<"GlassWindows Link\n";}
这样调用
typedef std::vector<std::tr1::shared_ptr<Window>> VPW; VPW winPtrs; winPtrs.push_back(std::tr1::shared_ptr<Window>(new GlassWinsow(2, 3))); for (VPW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter) { if(GlassWinsow* psw = dynamic_cast<GlassWinsow*>(iter->get())) psw->blink(); }
我们应该改成下面的调用方式
typedef std::vector<std::tr1::shared_ptr<GlassWinsow>> VPSW; VPSW winPtrs; winPtrs.push_back(std::tr1::shared_ptr<GlassWinsow>(new GlassWinsow(2, 3))); for (VPSW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter) { (*iter)->blink(); }
这种做法无法在同一个容器内存储指针“指向所有可能之各种Window派生类”。在Window上继承,那么就要定义多个类型的容器
②通过base class接口处理“所有可能之各种window派生类”,那就是在base class 里提供virtual函数做你想对各个Window派生类做的事。
修改后代码
class Window { public: Window(const int size) : _size(size) {} virtual ~Window() {} virtual void onResize(const int size) {_size = size;} int GetSize() const {return _size;} virtual void blink() {} private: int _size; }; class GlassWinsow : public Window { public: GlassWinsow(const int size, const int gSize) : Window(size), _gSize(gSize) {} void onResize(const int size, const int gSize) { Window::onResize(size); _gSize = gSize; } int GetGlassSize() const {return _gSize;} void blink() {cout<<"GlassWindows Link\n";} private: int _gSize; }; class WoodWindow : public Window { public: WoodWindow(const int size, const int gSize) : Window(size), _gSize(gSize) {} void onResize(const int size, const int gSize) { Window::onResize(size); _gSize = gSize; } int GetGlassSize() const {return _gSize;} void blink() {cout<<"WoodWindow Link\n";} private: int _gSize; };
调用
typedef std::vector<std::tr1::shared_ptr<Window>> VPSW; VPSW winPtrs; winPtrs.push_back(std::tr1::shared_ptr<Window>(new GlassWinsow(2, 3))); winPtrs.push_back(std::tr1::shared_ptr<Window>(new WoodWindow(4, 5))); for (VPSW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter) { (*iter)->blink(); }
GlassWindows Link
WoodWindow Link
绝对必须拒绝的是所谓的“连串(cascading)dynamic_casts,这样导致代码又大又慢
记住
1.如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast。如果有个设计需要转型动作,试着发展无需转型的替代设计。
2.如果转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需要将转型放进他们自己的代码内。
3.宁可使用C++ style(新式)转型,不要使用旧式转型。前者很容易辨认出来,而且也有着比较分门别类的职责。