27:尽量少做转型动作

这篇具有很好参考价值的文章主要介绍了27:尽量少做转型动作。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

转型(cast)破坏了类型系统。那可能导致任何种类的麻烦,有些容易辨识,有些非常隐晦。

一、转型语法 

(一)不同风格的语法

 1.C风格语法

C风格的转型动作:

(T)expression//将expression转型为T 

2.函数风格语法

函数风格的转型动作:

 T(expression)//将expression转型为T 

这两种形式并无差别,存粹只是小括号的摆放位置不同而已。称这两种形式为“旧式转型”(old-style cast)。

3.C++风格语法 

C++提供的四种新式转型(常常被称为new-style或C++-style cast):

1.const_cast<T>(expression)

2.dynamic_cast<T>(expression)

3.reinterpret_cast<T>(expression)

4.static_cast<T>(expression)

(二)C++风格语法详解 

上述四种转型方式各有不同的目的:

1.const_cast 

const_cast通常被用来将对象的常量性转除(cast away the constness)。它也是唯一有此能力的C++-style转型操作符。

2.dynamic_cast 

dynamic_cast主要用来执行“安全向下转型(safe downcasting)”,也就是用来决定某对象是否归属继承体系中的某个类型。它是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作。

3.reinterpret_cast 

reinterpret_cast意图执行低级转型,实际动作(及结果)可能取决于编译器,这也就表示它不可移植。例如,将一个pointer to int转型为一个int。

4.static_cast 

static_cast用来强迫隐式转换(implicit conversion),例如将non-const对象转为const对象,或将int转为double等等。它也可以用来执行上述多种转换的反向转换,例如将void*指针转为typed指针,将pointer-to-base转为pointer-to-derived。但它无法将const转为non-const——这个只有const_cast才办得到。

(三)尽量采用新式转型 

旧式转型仍然合法,但新式转型较受欢迎。原因是:

1.它们很容易在代码中被辨识出来(不论是人工辨识或使用工具如grep),因而得以简化“找出类型系统在哪个地点被破坏”的过程。

2.各转型动作的目标愈窄化,编译器愈可能诊断出错误的运用。例如若你打算将常量性去掉,除非使用新式转型中的const_cast否则无法通过编译。

二、转型的错误认识 

(一)转型做了某些动作 

某些程序员可能认为,转型其实什么都没做,只是告诉编译器把某种类型视为另一种类型。

这是错误的。任何一个类型转换(不论是通过转型操作而进行的显式转换,或通过编译器完成的隐式转换)往往真的令编译器编译出运行期间执行的码。

例如:

int x,y;
double d=static_cast<double>(x)/y;//x除以y,使用浮点数除法

 将int x转型为double几乎肯定会产生一些代码,因为在大部分计算器体系结构中,int的底层表述不同于double的底层表述。

又例如下面这个例子:

class Base{};
class Derived:public Base{};
Derived d;
Base* pb=&d;//隐喻地将Derived*转换为Base*

这里不过是建立一个base class指针指向一个derived class对象,但有时候上述的两个指针值并不相同。这种情况下会有个偏移量(offset)在运行期被施行于Derived*指针身上,用以取得正确的Base*指针值。

上个例子表明,单一对象(例如一个类型为Derived的对象)可能拥有一个以上的地址(例如“以Base*指向它”时的地址和“以Derived*”指向它时的地址。)实际上一旦使用多重继承,这事几乎一直发生着。即使在单一继承中也可能发生。

(二)不同语言的转型的适用性不同

另一件关于转型的事是:我们很容易写出某些似是而非的代码(在其他语言中也许真是对的)。

例如许多应用框架(appliciation framework)都要求derived class内的virtual函数代码的第一个动作就先调用base class的对应函数。假设有一个Window base class和一个SpecialWindow derived class,两者都定义了virtual函数onResize。进一步假设SpecialWindow的onResize函数被要求首先调用Window的onResize。

下面是实现方式之一,它看起来对,但实际上错:

class Window {
public:
	virtual void onResize(){}//base onResize实现代码
};
class SpecialWindow :public Window {
public:
	virtual void onResize() {//derived onResize实现代码
		static_cast<Window>(*this).onResize();//将*this转型为Window,
		                                     //然后调用其onResize,
											 //但这不可行
		//...//这里进行SpecialWindow专属行为
	}
};

上述程序将*this转型为Window,对函数onResize的调用也因此调用了Window::onResize。但实际上,他调用的并不是当前对象上的函数,而是稍早转型动作所建立的一个“*this对象的base class成分”的暂时副本身上的onResize。它不是在当前对象身上调用Window::onResize之后又在该对象身上执行SpecialWindow专属动作。它是在“当前对象的base class成分”的副本调用Window::onResize,然后在当前对象身上执行SpecialWindow专属动作。若Window::onResize修改了对象内容,当前对象其实没被改动,改动的是其副本。然而,若SpecialWindow::onResize内也修改对象,当前对象真的被改动。这将使当前对象进入一种“伤残”状态:其base class成分的更改没有落实,而derived class成分的更改落实了。

解决上述问题的方法是拿掉转型动作:

class SpecialWindow:public Window{
public:
    virtual void onResize(){
        Window::onResize();//调用Window::onResize作用于*this
    }
};

三、daynamic_cast探明

在探究dynamic_cast设计意涵之前,值得注意的是,dynamic_cast的许多实现版本执行速度相当慢。

例如,至少有一个很普遍的实现版本基于“class名称的字符串比较”,若你在四层深的单继承体系内的某个对象身上执行dynamic_cast,刚才说的那个实现版本所提供的每一次dynamic_cast可能会耗用多达四次的strcmp调用,用以比较class名称。深度继承或多重继承的成本更高。某些实现版本这样做有其原因(它们必须支持动态连接)。然而还是要强调,除了对一般转型保持机敏与猜疑,更应该注重效率的代码中对dynamic_cast保持机敏与猜疑。

之所以需要dynamic_cast,通常是因为你想在一个你认定为derived class对象身上执行derived class操作函数,但你的手上只有一个“指向base”的pointer或reference,你只能靠它们来处理对象。

有两个一般性做法可以避免这个问题。

(一)方法 

1.方法一

使用容器并在其中存储直接指向derived class对象的指针(通常是智能指针),如此便消除了“通过base class接口处理对象”的需要。

例如,假设先前的Window/SpecialWindow继承体系中只有SpecialWindow才支持闪烁效果,试着不要这样做:

class Window{};
class SpecialWindow :public Window {
public:
	void blink() {};
};
typedef std::vector<std::tr1::shared_ptr<Window>> VPW;

int main()
{
	VPW winptrs;
	for (VPW::iterator iter = winptrs.begin(); iter != winptrs.end(); ++iter)
	{
		//不希望使用dynamic_cast
		if (SpecialWindow* psw = dynamic_cast<SpecialWindow*>(iter->get()))
			psw->blink();
	}
 	return 0;
}

 应该这样做:

class Window{};
class SpecialWindow :public Window {
public:
	void blink() {};
};
typedef std::vector<std::tr1::shared_ptr<SpecialWindow>> VSPW;

int main()
{
	VSPW winptrs;
	for (VSPW::iterator iter = winptrs.begin(); iter != winptrs.end(); ++iter)
	{
		//不使用dynamic_cast
		(*iter)->blink();
	}
 	return 0;
}

这种做法使你无法在同一个容器内存储指针“指向所有可能的各种Window派生类”。若真要处理多种窗口类型,你可能需要多个容器,它们都必须具备类型安全性(type-safe)。

2.方法二

第二种做法可让你通过base class接口处理“所有可能的各种Window派生类”。那就是在base class内提供virtual函数做你想对各个Window派生类做的事。

例如,虽然只有SpecialWindow可以闪烁,但或许将闪烁函数声明于base class内并提供一份“什么也没做”的缺省实现码是有意义的:

class Window{
public:
	virtual void blink(){}//缺省实现代码,
	//但在后面的章节会告诉你为什么缺省实现代码可能是个馊主意
};
class SpecialWindow :public Window {
public:
	virtual void blink() {};
};
typedef std::vector<std::tr1::shared_ptr<Window>> VPW;

int main()
{
	VPW winptrs;
	for (VPW::iterator iter = winptrs.begin(); iter != winptrs.end(); ++iter)
	{
		//注意,这里没有dynamic_cast
		(*iter)->blink();
	}
 	return 0;
}

无论哪一种写法,都并非适用于所有情况,但在许多情况下它们都提供一个可行的dynamic_cast替代方案。

 (二)必须避免连串(cascading)dynamic_cast

绝对必须避免的一件事是所谓“连串(cascading)dynamic_cast”,也就是下述代码:

class Window{};
class SpecialWindow1 :public Window {
public:
	void blink() {};
};
class SpecialWindow2 :public Window {
public:
	void blink() {};
};
class SpecialWindow3 :public Window {
public:
	void blink() {};
};
typedef std::vector<std::tr1::shared_ptr<Window>> VPW;

int main()
{
	VPW winptrs;
	for (VPW::iterator iter = winptrs.begin(); iter != winptrs.end(); ++iter)
	{
		if(SpecialWindow1* psw1=dynamic_cast<SpecialWindow1*>(iter->get())){}
		if (SpecialWindow2* psw2 = dynamic_cast<SpecialWindow2*>(iter->get())) {}
		if (SpecialWindow3* psw3 = dynamic_cast<SpecialWindow3*>(iter->get())) {}

	}
 	return 0;
}

这样产生出来的代码又大又慢,而且基础不稳,因为每次Window class继承体系一有改变,所有这一类代码都必须再次检阅看看是否需要修改。

例如,一旦加入新的derived class,或许上述连串判断中需要加入新的条件分支。这样的代码应该总是以某些“基于virtual函数调用”的东西取而代之。

就像面对众多蹊跷可疑的构造函数一样,我们应该尽可能隔离转型动作,通常是把它隐藏在某个函数内,函数的接口会保护调用者不受函数内部任何肮脏龌龊的动作影响。

四、总结

1.若可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast。若有个设计需要转型动作,试着发展无需转型的替代设计。

2.若转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需将转型放进他们自己的代码内。

3.宁可使用C++-style(新式)转型,不要使用旧式转型。前者很容易辨识出来,而且也比较有着分门别类的职掌。 文章来源地址https://www.toymoban.com/news/detail-470010.html

到了这里,关于27:尽量少做转型动作的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • UE4动画系统,蒙太奇动画使用,添加动作

    提示:仅供学习参考 前言 一、什么是蒙太奇? 二、实现步骤 1.使用第三人称游戏c++模板创建一个项目,创建动画蒙太奇 2.在动画蓝图中添加蒙太奇 3.绑定鼠标左键输入  4.添加c++代码 5.设置动画蒙太奇  6.点击播放,鼠标右键就能看到挥手效果了 总结 本文介绍如何使用UE4的

    2024年02月05日
    浏览(52)
  • 硬盘坏了数据可以恢复吗?

    硬盘是一个数据容器,用来存储我们平时安装的软件、电影、游戏、音乐等等。但在使用过程中,如果出现一些故障,比如硬盘坏掉了。一旦出现这样的情况,会导致电脑上的数据丢失了,而这些数据经常出现一些比较重要的数据文件。那么 硬盘坏了数据可以恢复吗 ? 硬盘大

    2024年02月10日
    浏览(40)
  • 5招教你硬盘坏了数据恢复的方法!

    案例:硬盘坏了数据怎么恢复? “家人们,看看我,我的计算机硬盘出现了故障,但是还有很多重要的资料在里面,这可怎么办呢?我应该怎么恢复里面的重要数据呢?” 硬盘使用时间长了出现问题是很正常的,硬盘坏了数据恢复怎么做?这是很多人面临的问题。硬盘是计

    2024年02月09日
    浏览(42)
  • 像游戏一样办公,赋能OA系统转型

    办公币 OAC ,是一种以区块链为基础的,基于P2P形式的加密货币。在新型OA系统中员工通过完成不同的待办任务,OACoin下发中心会给予相应的代币奖励,获得的货币还可以在区块链中进行交易。打破传统枯燥OA办公模式,激发员工办公积极性,像游戏一样办公,赋能OA系统转型

    2024年02月12日
    浏览(35)
  • 突然断网了 怎样判断路由器是不是坏了?

    网络连接突然断了,无法连接上网,但是用猫直接连接网线可以正常上网,使用路由器无法连接,wan灯不亮,在尝试过多种方法后,有可能就是路由器出问题了。 1、检查路由器设置和网络设置,主要是IP和DNS,光猫和路由器的地址设置成不同的IP。 2、如果确定自己的设置没

    2024年02月07日
    浏览(45)
  • 电脑坏了去维修,第一家报价800,第三家说报废!

    这篇文章主要讲的是修理坏掉的电脑。 第一家报价300,第二家报价800,第三家说要报废! 相信很多朋友对于修电脑坏了要多少钱有很多困惑,修电脑坏了要多少钱,到底去正规售后服务还是去非品牌店维修一台坏掉的电脑。 今天高才胜就来给大家讲讲。 讨论修理损坏的计算

    2024年03月18日
    浏览(62)
  • iPhone无法关机未必是坏了!如何修复无法关闭的iPhone

    iPhone运行很慢且发热是一个比较罕见的情况,但如果它发生在你身上,下面解释了发生的原因以及你如何修复它。 iPhone无法关闭的最可能原因是: 由于软件问题,它被冻结了。 睡眠/唤醒按钮坏了。 屏幕坏了,对敲击没有反应。 这些说明适用于所有型号的iPhone。 在你尝试这

    2024年02月06日
    浏览(51)
  • 【难受坏了】Win11, 快连APP断网(Win11,lets威屁N)

    用的是带客户端的V屁N,最近买了菊牌笔记本之后安装,一连接就断网,右下角就变成地球图标,跳板对应的网络适配器端口显示只进不出,尝试了手动配置DNS,开闭IPV6选项,刷新netsh winsock等等建议无果,最终重装系统,仍旧无果。

    2024年02月16日
    浏览(38)
  • 27基于java的学生在线考试系统

    随着互联网迅速发展,人们的生活已经越来越离不开互联网,人们足不出户就可以工作、学习等。对于在校学生,通过网络教育不仅可以随时进行网络学习,也可以根据学习的情况自我检测,有利于学生高效、快捷地掌握所学的知识。 本系统预设计的基于网络的学生自测系统

    2024年02月03日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包