這篇主要是談 rvalue reference + implementation class 的結合可以產生什麼樣的效果,在 C++,由於 header file 必須定義好 class 的 declaration -- 包含使用者不需要知道的 private members,因此創出了 implementation class 這東西負責撰寫並處理原始 class 要做的事情,而要公開的另一個 class 就只有一個 private member 是 implementation class 產生的 object,而 public member 就是 implementation class 的 public interface。這個小技巧原本就如同前面所說,只是個用來 client 不會看到不用知道的東西,除此之外確實是沒什麼效果,但是 rvalue reference 的出現讓這個效果可以有提升效能的作用。
關於 implementation class 還是先來舉個例吧! 假設原本想要寫出一個這樣的 class
class A
{
private:
int x;
void reset();
public:
void setX(const int new_x);
int getX() const;
};
利用 implementation class 的話可以改寫成這樣
class AImpl
{
private:
int x;
void reset();
public:
void setX(const int new_x);
int getX() const;
};
class A
{
private:
AImpl* aimpl;
public:
A();
A(const A& rhs);
~A();
A& operator= (const A& rhs);
inline void setX(const int new_x) const { aimpl->setX(new_x); }
inline int getX() const { return aimpl->getX(); }
};
如此一來就可以把 AImpl 藏在別的地方,client 就只看得到 A 裡的東西囉! 這邊我把 ctor、copy ctor 還有 dtor 的實作部分拿掉不寫只是因為我懶 + 那邊確實沒什麼好說的XD assignment operator 這邊比較有趣一點,所以我拿出來另外寫:
A& A::operator= (const A& rhs)
{
A temp = new A(rhs);
swap(aimpl, temp.aimpl);
return *this;
}
這邊 tricky 的地方在這裡:原本 assignment operator 要避免 self assignment 並且寫出類似這樣的東西:
if(this == &rhs) return *this;
這種寫法可以省掉上面那樣的檢查條件,並且還可以滿足 strong exception safty condition -- commit or rollback,不過我這邊把 exception 的部分拿掉了,請自行補上XD。關於這部分之後再找時間補充,C++ 的 exception 有滿多東西可以講的,特別是在 class 架構設計上以及 exception safty 的議題討論上,因為 exception 的影響甚巨,如果沒有在設計架構的開始就納入考量,事後也難以,甚至是無法補上的。
至此為止是 implementation class 的一般形式,rvalue reference 的影響主要在兩個:copy ctor 以及 assignment operator,考慮以下情況 (假設 A 有 overload operator+):
A w, x, y, z;
1. w = x + y + z;
2. A a = x + y + z;
注意,第二種情形最後的 assignment 是呼叫 copy ctor 而不是 assignment operator。
首先我們知道這種加法運算會產生許多的暫時物件,所以上一篇文章有提到說可以利用 rvalue reference 來減少複製的次數。這邊有趣的點在最後一個 assignment:如果今天 assignment operator 是像上面那樣定義,當然這個 assignment 也還會有一次複製的行為,但其實仔細想想就會發現,那個複製根本沒有必要! 原因如下:
1. A 裡面的實作改用 implementation class,所以只有一個指標 aimpl
2. 暫存物件一定會被摧毀 (給個名稱比較方便,就取成 tmp 吧!),但是如果有辦法把 w 跟 tmp 的 aimpl 給交換,而且 w 的 aimpl 也本來就要被銷毀,如此一來就可以避免這次這次的複製,從而省下不少時間!
rvalue reference 讓上面所說的想法得以實現,只需準備以下兩個 function
A& A::operator= (A&& rhs)
{
swap(aimpl, rhs.aimpl);
return *this;
}
A(A&& rhs) : aimpl(NULL)
{
swap(aimpl, rhs.aimpl);
}
如此一來,當 compiler 發現可以調用上面那兩個版本的 function 時 (也就是有暫存物件產生時),就會直接將他們內部的指標作交換,從而避免多餘的複製行為。如果對 C++ 的敏感度夠高的話,會發現 rvalue reference 挺好用的,可以在一些小細節降低負擔,從而提高整體程式的效能。
沒有留言:
張貼留言