C 臨時物件(2)

2021-05-27 08:18:51 字數 3239 閱讀 1624

與臨時物件的鬥爭(下)

原載:www.cnblos.com/liyiwen 在

上篇裡,我們看到了 (n)rvo 和右值引用,下面我們來看看表示式模板。

如果有「系統地」學習過 c++ 的模板程式設計,那麼你應該已經知道 expression template 這個「東西」。在模板聖經《c++ templates》的第 18 章專門用了一整章來講這個技巧,(是的,我認為它是一種技巧)。足以見得它比較複雜,也很重要。

說起 expression template 產生,「臨時物件」也是「功臣」之一啊。還是來用例子來說明(你能很容易找到這樣類似的例子,呵,我就是參照著別人寫的):

view plain

copy to clipboard

print

?class myvec  

myvec(myvec const& a_left)   

~myvec()  

myvec& operator=(myvec const& a_left)   

return *this;  

}  int& operator (size_t a_idx)   

intoperator (size_t a_idx)const   

myvec const

operator + (myvec const& a_left) const   

myvec& operator += (myvec const& a_left)   

return *this;  

}  private:  

static

intconst size = 100;  

int* p;  

};  

int main(int argc, char* argv)  

看,我們寫下這麼小段**:

view plain

copy to clipboard

print

?

這是很常用的數**算吧,而且**很直觀。但這個表示式有乙個問題,就是產生了「不必要」的臨時物件。因為 a + b 的結果會成為乙個存在乙個臨時物件上 temp 上,然後這個 temp 再加上 c ,最後把結果傳給 d 進行初始化。如果這些向量很長,或是表示式再加幾節,可以想像這些 temp 會多讓人不爽。

而且,如果我們寫成這樣:

myvec d = a;

d += b;

d += c;

就可以避免產生多餘的臨時物件。但這樣寫,如果是不了解「**」的人看了myvec d = a + b + c;之後再看這段,是不是會覺得寫這**的人欠k?

好吧,你會問,上面不是說右值引用可以解決這樣問題?是的,但在沒有右值引用的「黑暗日子」裡,我們就不用過活了?當然要,小學開始數學老師就教我們要一題多解吧,換個思路也有辦法,這個辦法就是et。

怎麼做的呢?a + b + c 會產生臨時變數是因為 c++ 是即時求值的,在看到 a + b,就先算出乙個 temp 的vector物件,然後再向下算。如果能進行延遲求值,看完整個表示式再來計算,那麼就可以避免這個temp的產生。

怎麼做?

原來的做法中,operator + 直接進行了計算,既然我們不想它「過早」的計算,那麼我們就在重新過載乙個operator + 運算子,在這個運算中不進行真正的運算,只是生成乙個物件,在這個物件中把加法運算子兩邊的運算元保留下來~然後讓它參與到下一步的計算中去。(好吧,這個物件也是臨時的,但它的代價非常非常小,我們先不理會它)。於是我們寫下面的**:

view plain

copy to clipboard

print

?

比起之前的**來說,這段**有幾個重要的修改:首先,我們增加了乙個模板類 expplus,用它來代表加法計算的「表示式」,但在進行加法時,它本身並不進行真正的計算。對這個類,定義了下標運算子,這個運算子中才進行了真正的加法計算。然後,對於原來的 myvec,我們過載它的賦值運算子,讓它在賦值的時候通過expplus的下標運算子來獲得計算結果(也就是,在賦值操作時才真正的進行了計算!)。

上面這段話,對於不了解et的人來說,也許一時間還不容易明白,我們一步一步來:

在 d = a + b + c 這個式子中,首先遇到 a + b,這時,模板函式 operator + 會被呼叫(**中注釋了「point 1 」),這時只是生成乙個臨時的expplus物件(我們叫它 t1 吧),不做計算,只是保留計算的左右運算元(也就是a和b),接著,t1 + c ,再次呼叫同樣的 operator + ,而且也只是生成乙個物件(我們叫它 t2 吧),這個物件的型別是 expplus>,同樣,t2 在這裡只是保留了兩邊的運算元(也就是 t1 和 c)。直到整個表示式「做完」,沒有任何東西進行了計算,所做的事情實際上只是用 expplus 這個模板類把計算式的資訊記錄下來了(當然,這些資訊就是參與計算的運算元)。

最後,當進行 d = t2 的時候,myvec 的賦值運算子被呼叫(用 t2 作引數)。注意,這個呼叫中的語句 p[i] = t2[i],其中 t2[i] 通過過載的下標運算子,展開成 t1[i] + c[i],同理 t1[i] 又再次展開,成為 a[i]+b[i],最終,p[i] = t2[i] 就變成了:p[i] = a[i] + b[i] + c[i])(當然,裡面參雜了內聯的效果,這些函式都是非常容易被內聯的)。就像變「魔術」一樣,我們通過expplus完成了「延遲計算」,並避免了大型的 myvec 臨時物件的產生。

這基本上就是 et 的「原理」了吧。我們來「專門化」一下 et 的好處:

這樣,用 et 就能兼顧到「直觀」和「效率」了。

et 中 c++ 中的類庫里已經有非常多的應用了(包括 boost 中的多個子庫,以及 blitz++ 等高效能數學庫)

(n)rvo 是編譯器為我們做的優化手段,在能進行優化的情況下,nrvo 的表現是非常好的,因為它才真正的避免了臨時物件的產生(rvalue reference 和 expression template 中都可能還存在一些小型臨時物件),但 (n)rvo 有很多的限制條件。右值引用(rvalue reference )和 move 語意彌補了 (n)rvo 的不足之處,使得臨時物件的開銷最小化成為可能,但這也是有侷限的,比如,嗯,如果乙個類本身不動態地擁有資源……,那 move 就沒有意義了。expression template 保持了表示式直觀和效率兩者,很強大,但很顯然它太複雜,主要是作為類庫的設計者的**。另外,它也可能使得使用者要理解一些「新」東西,比如,如果我想儲存表示式的中間值,那麼 ...> 一定會讓我很頭大(不過有了 c++0x 的 auto 就好多了,呵呵)。

C 臨時物件(2)

與臨時物件的鬥爭 下 原載 www.cnblos.com liyiwen 在 上篇裡,我們看到了 n rvo 和右值引用,下面我們來看看表示式模板。如果有 系統地 學習過 c 的模板程式設計,那麼你應該已經知道 expression template 這個 東西 在模板聖經 c templates ...

C 臨時物件

臨時物件的產生 1.用建構函式作為隱式型別轉換函式時。2.建立乙個沒有名字的物件時。直接寫下 ctempobj 輸出 init obj exit obj 不單調用了建構函式,還呼叫了析構函式.既然是物件也可以這樣使用 ctempobj fun 不過這樣使用可要小心了 new ctempobj fun...

C 臨時物件

c 中有這樣一種物件 它在 中看不到,但是確實存在。它就是臨時物件 由編譯器定義的乙個沒有命名的非堆物件 non heap object 為什麼研究臨時物件?主要是為了提高程式的效能以及效率,因為臨時物件的構造與析構對系統效能而言絕不是微小的影響,所以我們應該去了解它們,知道它們如何造成,從而盡可能...