Effective C++学习笔记总链接
改善程序与设计的55个具体做法学习笔记-每日1条
条款27:尽量少做转型动作
【技巧】
1. 如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast。如果有个设计需要转型动作,试着发展无需转型的替代设计。
2. 如果转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需要将转型放进他们自己的代码中。
3. 宁可使用C++ -style(新式)转型,不要使用旧式转型。前者很容易辨识出来,而且也比较有着分门别类的职掌。
转型破坏了类型系统。
旧式转型
(T) expression // C风格转型,将expression 转型为T
T(expression) // 函数风格转型,将expression 转型为T
两种转型并无差别,被称为“旧式转型”
新式转型(new-style或C++ -style casts)
const_cast(expression)
const_cast 通常被用来将对象的常量性转除。将const转型为non-const。
dynamic_cast(expression)
dynamic_cast主要用来执行“安全向下转型”,也就是用来决定某对象是否归属继承体系的某个类型。
它是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作。
dynamic_cast 的许多实现版本执行速度相当慢。例如:基于class名称之字符串比较,如果你在四层深的单继承体系内的某个对象上执行dynamic_cast ,每一次dynamic_cast可能会耗用多达4次strcmp调用。深度继承或多重继承的成本更高。
reinterpret_cast(expression)
执行低级转型,实际动作(及结果)可能取决于编译器,它不可移植。
static_cast (expression)
static_cast 用来强迫隐式转型,例如将non-const对象转为const,或将int 转型为double,它可以执行上述多种转换的反向转换。但它无法将const转换为non-const ------只有const_cast 才可以。
比较
旧式转型依然合法,但新式转型更受欢迎。原因:
- 新式转型很容易在代码中被辨识出来,因而得以简化“找出类型系统在哪个地方被破坏”。
- 各转型动作的目标愈窄化,编译器愈可能诊断出错误的运用。如果你要将常量性去掉,除非使用新式转型中的const_cast 否则无法通过编译。
唯一使用旧式转型的时机是:当要调用一个explicit构造函数将一个对象传递给一个函数时。
class Widget{
public:
explicit Widget(int size);
...
};
void doSomeWork(const Widget& w);
doSomeWork(Widget(15)); // 以一个int加“函数风格”的转型动作创建一个Widget
doSomeWork(static_cast<Widget>(15)); // 以一个int加“C++ 风格”的转型动作创建一个Widget
从某个角度讲,蓄意的“对象生成”动作感觉不怎么像“转型”,但始终理智地使用新型转型。
任何一种类型转换,往往真的令编译器编译出运行期间执行的码。
对象的布局方式和它们的地址计算方式随编译器的不同而不同,那意味“由知道对象如何布局”而设计的转型,跨平台可能行不通。
转型在派生类中的错误使用
class Window // base class
{
public:
virtual void onResize()
{...}
...
};
class SpecialWindow: public Window
{
public:
virtual void onResize()
{
static_cast<Window>(*this).onResize(); // 将*this转型为Window,然后调用其onResize(),这不可行
... // SpecialWindow的专属动作
}
};
上述代码,是不可行的,它调用的并不是当前对象上的函数,而是转型动作所建立的一个“*this对象之base class成分”的暂时副本身上的onResize()。当前对象其实没有改动,改动的是副本。
该如何实现派生类对象调用基类函数呢?
class SpecialWindow: public Window
{
public:
virtual void onResize()
{
Window::onResize(); // 调用Window::onResize()作用在*this身上
... // SpecialWindow的专属动作
}
};
这个例子也说明,如果你发现自己打算转型,你可能正将局面发展至错误方向上。
替代dynamic_cast的两种做法
需要dynamic_cast的情形:你认定为derived class对象,但你手上只有指向base的pointer和reference
1.使用容器并在其中存储直接指向derived class对象的指针(通常为智能指针)
这样便可以消除“通过base class 接口处理对象”的需要。容器必须具备类型安全性
class Window{ ... };
class SpecialWindow:public Window
{
public:
void blink();
...
};
typedef std::vector<std::shared_ptr<Window>> VPW;
VPW winPtrs;
for(VPW::iterator iter = winPtrs.begin(); iter!=winPtrs.end(); ++ iter)
{ // 不希望使用dynamic_cast
if(SpecialWindow* psw = dynamic_cast<SpecialWindow*>(iter->get()))
psw-> blink();
}
应该改成这样:
typedef std::vector<std::shared_ptr<SpecialWindow>> VPSW;
VPSW winPtrs;
...
for(VPSW::iterator iter = winPtrs.begin(); iter!=winPtrs.end(); ++ iter)
{
(*iter)-> blink(); // 不使用dynamic_cast
}
2.virtual函数继承
class Window{
virtual void blink(){}
...
};
class SpecialWindow:public Window
{
public:
virtual void blink(){}
...
};
typedef std::vector<std::shared_ptr<Window>> VPW;
VPW winPtrs;
...
for(VPW::iterator iter = winPtrs.begin(); iter!=winPtrs.end(); ++ iter)
{
(*iter)-> blink();
}
【注意】:绝对必须避免的是“连串“使用dynamic_cast。这样的代码应该总是以某些“基于virtual函数”代替。
切记,优良的C++代码很少使用转型。