5.实现 Implementations

大多数情况下,适当提出你的classes(和class templates)定义以及functions (和function templates)声明,是花费最多心力的两件事。一旦正确完成它们,相应的实现大多直截了当。尽管如此,还是有些东西需要小心。太快定义变量可能造成效率上的拖延;过度使用转型(casts)可能导致代码变慢又难维护,又招来微妙难解的错误;返回对象“内部数据之号码牌(handles)”可能会破坏封装并留给客户虚吊号码牌(dangling handles);未考虑异常带来的冲击则可能导致资源泄漏和数据败坏;过度热心地inlining可能引起代码膨胀;过度耦合(coupling)则可能导致让人不满意的冗长建置时间(build times)。

所有这些问题都可避免。本章逐一解释各种做法。

条款26:尽可能延后变量定义式的出现时间 Postpone variable definitions as long as possible.

只要你定义了一个变量而其类型带有一个构造函数或析构函数,那么当程序的控制流(control flow)到达这个变量定义式时,你便得承受构造成本;当这个变量离开其作用域时,你便得承受析构成本。即使这个变量最终并未被使用,仍需耗费这些成本,所以你应该尽可能避免这种情形。

或许你会认为,你不可能定义一个不使用的变量,但话不要说得太早!考虑下面这个函数,它计算通行密码的加密版本而后返回,前提是密码够长。如果密码太短,函数会丢出一个异常,类型为 logic_error(定义于 C++标准程序库,见条款54):

//这个函数过早定义变量"encrypted"
std::string encryptPassword(const std::string& password)
{
	using namespace std;
	string encrypted;
	if(password.length() < MinimumPasswordLength) {
		throw logic_error("Password is to short");
	}
	...		//必要动作,必能将一个加密后的密码置入变量encrypted内
	
	return encrypted;
}

对象encrypted在此函数中并非完全未被使用,但如果有个异常被丢出,它就真的没被使用。也就是说如果函数 encryptPassword 丢出异常,你仍得付出encrypted的构造成本和析构成本。所以最好延后encrypted的定义式,直到确实需要它:

//这个函数延后"encrypted"的定义,直到真正需要它
std::string encryptPassword(const std::string& password)
{
	using namespace std;
	if(password.length() < MinimumPasswordLength) {
		throw logic_error("Password is to short");
	}
	string encrypted;
	...		//必要动作,必能将一个加密后的密码置入变量encrypted内
	
	return encrypted;
}

但是这段代码仍然不够秾纤合度,因为encrypted虽获定义却无任何实参作为初值。这意味调用的是其default构造函数。许多时候你该对对象做的第一次事就是给它个值,通常是通过一个赋值动作达成。条款4曾解释为什么“通过default构造函数构造出一个对象然后对它赋值”比“直接在构造时指定初值”效率差。那个分析当然也适用于此。举个例子,假设encryptPassword的艰难部分在以下函数中进行:

void encrypt(std::string &s); //在其中的适当地点对s加密

于是encryptPassword可实现如下,虽然还不算是最好的做法:

//这个函数延后"encrypted"的定义,直到真正需要它
//但此函数仍然有着不该有的效率低落。
std::string encryptPassword(const std::string& password)
{
	...							//检查length,如前。
	std::string encrypted;		//default-construct encrypted
	encrypted = password;		//赋值给encrypted
	encrypt(encrypted);
	return encrypted;
}

更受欢迎的做法是以password作为encrypted的初值,跳过毫无意义的default构造过程:

//终于,这是定义并初始化encrypted的最佳数法
std::string encryptPassword(const std::string& password)
{
	...										//检查length,如前。
	std::string encrypted(password);		//通过copy构造函数定义并初始化
	encrypt(encrypted);
	return encrypted;
}

这让我们联想起本条款所谓“尽可能延后”的真正意义。你不只应该延后变量的定义,直到非得使用该变量的前一刻为止,甚至应该尝试延后这份定义直到能够给它初值实参为止。如果这样,不仅能够避免构造(和析构)非必要对象,还可以避免无意义的default构造行为。更深一层说,以“具明显意义之初值”将变量初始化,还可以附带说明变量的目的。

“但循环怎么办?”你可能会感到疑惑。如果变量只在循环内使用,那么把它定义于循环外并在每次循环迭代时赋值给它比较好,还是该把它定义于循环内?也就是说下面左右两个一般性结构,哪一个比较好?

//方法A:定义是循环外
Widget w;
for(int i = 0; i < n; ++i) {
	w = 取决于i的某个值;
	...
}

//方法B:定义是循环内
for(int i = 0; i < n; ++i) {
	Widget w = 取决于i的某个值;
	...
}

这里我把对象的类型从string改为Widget,以免造成读者对于“对象执行构造、析构、或赋值动作所需的成本”有任何特殊偏见。

在Widget函数内部,以上两种写法的成本如下:

■ 做法A:1个构造函数+1个析构函数+n个赋值操作

■ 做法B:n个构造函数+n个析构函数

如果classes的一个赋值成本低于一组构造+析构成本,做法A大体而言比较高效。尤其当n值很大的时候。否则做法B或许较好。此外做法A造成名称w的作用域(覆盖整个循环)比做法B更大,有时那对程序的可理解性和易维护性造成冲突。因此除非 (1) 你知道赋值成本比“构造+析构”成本低,(2) 你正在处理代码中效率高度敏感(performance-sensitive)的部分,否则你应该使用做法B。

请记住

■ 尽可能延后变量定义式的出现。这样做可增加程序的清晰度并改善程序效率。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
请按以下描述,自定义数据结构,实现一个circular bufferIn computer science, a circular buffer, circular queue, cyclic buffer or ring buffer is a data structure that uses a single,fixed-size buffer as if it were connected end-to-end. This structure lends itself easily to bufering data streams. There were earlycircular buffer implementations in hardware. A circular buffer first starts out empty and has a set length. In the diagram below is a 7-element buffeiAssume that 1 is written in the center of a circular buffer (the exact starting locatiorimportant in a circular buffer)8Then assume that two more elements are added to the circulal2buffers use FIFOlf two elements are removed, the two oldest values inside of the circulal(first in, first out) logic. n the example, 1 8 2 were the first to enter the cremoved,leaving 3 inside of the buffer.If the buffer has 7 elements, then it is completely full6 7 8 5 3 4 5A property of the circular buffer is that when it is full and a subsequent write is performed,overwriting the oldesdata. In the current example, two more elements - A & B - are added and theythe 3 & 475 Alternatively, the routines that manage the buffer could prevent overwriting the data and retur an error or raise an exceptionWhether or not data is overwritten is up to the semantics of the buffer routines or the application using the circular bufer.Finally, if two elements are now removed then what would be retured is not 3 & 4 but 5 8 6 because A & B overwrote the 3 &the 4 yielding the buffer with: 705A
05-26

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值