天天看點

[C++]異常進行中的拷貝構造操作(copy constructor)

*書生注:就算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)如果可以的話,盡可能采用匿名臨時變量的寫法。

繼續閱讀