接續昨日([Day 15] Handle assignment to self in operator= (1))內容,我們看到需要確保self-assignment-safe,並且可以以identity test來實現,那還有沒有其它要注意的地方或其他作法呢?
昨天的改法,雖然確保了self-assignment-safe,但卻沒有避免 exception-unsafe。
當new Bitmap
報exception的時候(可能memory不足,或Bitmap
自己本身的copy constructor報exception),仍有可能變成讓Widget
的pb
指向一個被刪除的物件。這會有什麼後果呢?我們根本不能正常使用它!
值得慶幸的是如果我們能確保exception-safe
,它通常也能連帶保證self-assignment-safe
,所以大家通常直接專注於確保exception-safe,這個在後面的守則會有更深入的介紹,而在目前,我們可以觀察到用一些順序調整,來達到exception-safe的效果:
Widget& Widget::operator=(const Widget& rhs)
{
Bitmap *pOrig = pb;
pb = new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}
可以看到這邊的做法是先做一個原本bitmap的copy並指向它,之後指向新的成功之後才去刪除原本的bitmap,所以就算new
的過程中出現exception也沒事;而此時如果回到assign給自己的情況,也沒有問題。
此時,可能會覺得雖然assign給自己沒問題,但多了無謂的操作(先copy再指回去再刪除),沒有效率,那也可以再把identity test加回去;然而,我們在考慮最有效的寫法時,應該也要思考這個情形出現的頻率,因為identity test也是有代價的,code變多了,flow變複雜了,這都可能會降低runtime的速度,也有可能降低code base變大複雜度變高一系列的負面影響。
還有一種確保exception跟self-assignment-sage的方法,就是採用 "copy and swap"。這個同樣會在後面詳細介紹,但因為這個做法很常見,我們可以先看看它通常怎麼做就好:
class Widget
{
void swap(Widget& rhs); // exchange *this and rhs data
}
Widget& Widget::Operator=(const Widget& rhs)
{
Widget temp(rhs); // make a copy of rhs's data
swap(temp); // swap *this data with the copy data
return *this;
}
它有一種變形的寫法長這樣:
Widget& Widgget::operator=(Widget rhs)
{
swap(rhs);
return *this;
}
這利用了copy assignment 也可以被宣告為take argument by value,又by value的時候就會make a copy的特性,直接一行搞定!這種寫法讓copy的步驟從function body移到了parameter的construction,可能可以編譯出更有效率的code。
貼心重點提醒:
- Make sure that any function operating on more than one object behaves correctly if two or more of the objects are the same
- Make sure operator= is well-behaved when an object is assigned to itself. Techniques include comparing addresses of source and target objects, careful statement ordering, and copy-and-swap.
總結要確保self-assignment-safe的常見左法有三種:1. identity test, 2. 調整寫法 3. copy-and-swap;如果self-assigment 發生頻率不高,我們可以專注於確保exception-safe即可。