*书生注:就算More Effective C++的读后感吧
[问题]
下面这段代码中,类型T的复制拷贝操作(copy constructor)一共被调用几次?
[问题]
下面这段代码中,类型T的复制拷贝操作(copy constructor)一共被调用几次?
如何改进来减少调用次数?能减少到几次?
class T {
public:
T(constT& t) {}
T() {}
};
void f() throw(T) {
T t;
throw t;
}
int main() {
try {
f();
} catch (Tt) {
throw t;
}
}
[解答]
答案是:3次。
那么能减少到几次呢?恩,1次。某些编译器上甚至可以到0次。
让我们一个一个来看。
明白了原因,也就知道如何来改进了。
让我们按照由易到难的顺序,也就是,倒过来说。
[第三次]
当throw一个明确对象的时候,一定会发生复制。
道理很简单,如果把当前的auto变量(在我们这里是t)直接throw出去的话,
在紧接着离开函数f的作用域时,auto变量会被退栈过程销毁(析构),
那么外层的代码得到的异常就可能是一个已经仙去的对象(oh!)。
就我们这里的代码而言,针对对象t,会做一个拷贝构造操作(copy constructor),
产生一个新的对象出来,用来抛出去。
就这里的代码而言,有什么办法来改进吗?
有,那就是不带任何对象的throw语句,重新抛出当前的异常,避免发生复制操作。
[第二次]
当catch一个异常的时候,如果采用by value的方式,一定会发生复制。
原理和函数传参方式类似。
同样的道理,我们可以采用by reference方式来避免多余的复制操作。
(就异常而言,复制操作完全是多余的。而且为reference加上const修饰符也是多余的。
因为接收到的异常本身就已经是一个复制后的对象。)
[第一次]
这里的复制和第三次类似,但是这里我们必须抛出一个明确的异常,
不能像第三次的解决办法那样重新抛出当前的异常(因为还没有异常发生!),
似乎没有什么可改进的了,是吗?
不是的,c++允许编译器针对临时变量(temp variable)进行优化,
所以这里我们可以通过一个匿名临时变量来处理。
throw T();
编译器就有机会可以优化掉中间产生的临时变量,直接将default construtor创建的对象抛出去。
(不保证所有编译器都会做此优化,但,匿名临时变量至少写法上更简洁,不是吗?)
下面是修改后的代码(在g++ 4.1.2上验证通过)
[总结]
(1)采用by reference方式来catch异常。
(2)通过throw来重新抛出异常。
(3)如果可以的话,尽可能采用匿名临时变量的写法。
答案是:3次。
那么能减少到几次呢?恩,1次。某些编译器上甚至可以到0次。
让我们一个一个来看。
明白了原因,也就知道如何来改进了。
class T {
public:
T(constT& t) {}
T(){}
};
void f() throw(T) {
T t;
throw t; //第一次!
}
int main(){
try {
f();
} catch (T t){ //第二次!
throw t; //第三次!
}
}
让我们按照由易到难的顺序,也就是,倒过来说。
[第三次]
当throw一个明确对象的时候,一定会发生复制。
道理很简单,如果把当前的auto变量(在我们这里是t)直接throw出去的话,
在紧接着离开函数f的作用域时,auto变量会被退栈过程销毁(析构),
那么外层的代码得到的异常就可能是一个已经仙去的对象(oh!)。
就我们这里的代码而言,针对对象t,会做一个拷贝构造操作(copy constructor),
产生一个新的对象出来,用来抛出去。
就这里的代码而言,有什么办法来改进吗?
有,那就是不带任何对象的throw语句,重新抛出当前的异常,避免发生复制操作。
[第二次]
当catch一个异常的时候,如果采用by value的方式,一定会发生复制。
原理和函数传参方式类似。
同样的道理,我们可以采用by reference方式来避免多余的复制操作。
(就异常而言,复制操作完全是多余的。而且为reference加上const修饰符也是多余的。
[第一次]
这里的复制和第三次类似,但是这里我们必须抛出一个明确的异常,
不能像第三次的解决办法那样重新抛出当前的异常(因为还没有异常发生!),
似乎没有什么可改进的了,是吗?
不是的,c++允许编译器针对临时变量(temp variable)进行优化,
所以这里我们可以通过一个匿名临时变量来处理。
throw T();
编译器就有机会可以优化掉中间产生的临时变量,直接将default construtor创建的对象抛出去。
(不保证所有编译器都会做此优化,但,匿名临时变量至少写法上更简洁,不是吗?)
下面是修改后的代码(在g++ 4.1.2上验证通过)
class T {
public:
T(constT& t) {}
T(){}
};
void f() throw(T) {
throw T(); //没有复制!(某些编译器下)
}
int main(){
try {
f();
} catch (T& t){ //没有复制!
throw; //没有复制!
}
}
[总结]
(1)采用by reference方式来catch异常。
(2)通过throw来重新抛出异常。
(3)如果可以的话,尽可能采用匿名临时变量的写法。