Effective C++ T27:尽量少做转型动作

38 篇文章 0 订阅
31 篇文章 2 订阅

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++代码很少使用转型

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值