*書生注:就算More Effective C++的讀後感吧
[問題]
下面這段代碼中,類型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次。
讓我們一個一個來看。
明白了原因,也就知道如何來改進了。
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)如果可以的話,盡可能采用匿名臨時變量的寫法。