2015-01-05 73 views
2

爲了實現*運營商的完美轉發,我構建了以下示例。完美轉發和臨時對象的範圍

#include <string> 
#include <iostream> 

class A { 
public: 
    std::string name; 
    A(const A& _other) : name(_other.name) { 
     std::cout << "Copy-Construct with name: " << name << std::endl; 
    } 
    A(A&& _other)  : name(std::move(_other.name)) { 
     std::cout << "Move-Construct with name: " << name << std::endl; 
    } 
    A(std::string _name): name(_name) { } 
}; 

A operator*(const A& _lhs, const A& _rhs) { 
    std::cout << "Start Operator Copy with: " << _lhs.name << " " << _rhs.name << std::endl; 
    A bla(_lhs.name+" "+_rhs.name); 
    return bla; 
} 

A&& operator*(A&& _lhs, const A& _rhs) { 
    std::cout << "Start Operator Move with: " << _lhs.name << " " << _rhs.name << std::endl; 
    _lhs.name += " "+_rhs.name; 
    return std::move(_lhs); 
} 

int main() { 
    A a("a"); 
    A b("b"); 
    A c("c"); 
    A d("d"); 

    A x = a*b*A("t1")*c*A("t2")*A("t3")*d; 

    std::cout << "Final result is: " << x.name << std::endl; 
} 

結果如我所願,特別是只有一個移動構造函數和沒有拷貝構造函數被調用。

Start Operator Copy with: a b 
Start Operator Move with: a b t1 
Start Operator Move with: a b t1 c 
Start Operator Move with: a b t1 c t2 
Start Operator Move with: a b t1 c t2 t3 
Start Operator Move with: a b t1 c t2 t3 d 
Move-Construct with name: a b t1 c t2 t3 d 
Final result is: a b t1 c t2 t3 d 

現在我的問題是:這是代碼?特別是我可以依靠這樣一個事實:第一個臨時對象(由a和b構造而成)以分號而不是在那之前離開其範圍?並且是構造,將作爲移動參考獲得的對象返回爲移動參考,合法嗎?

+0

你指的是哪個臨時對象? – Pradhan

+5

第一次重載泄漏內存 - 這絕對不是在任何意義上的「完美」。兩個重載都應該按值返回。 –

+0

@JonathanWakely:非常抱歉,那是另一個測試案例。我糾正了它。輸出正確無誤。 – Haatschii

回答

3
A&& operator*(const A& _lhs, const A& _rhs) { 
    std::cout << "Start Operator Copy with: " << _lhs.name << " " << _rhs.name << std::endl; 
    A* bla = new A(_lhs.name+" "+_rhs.name); 
    return std::move(*bla); 
} 

這創建了一個動態分配的對象,所以調用者負責刪除它。你的例子沒有做到這一點,所以泄漏內存。這是一個可怕的功能。它應該返回值,而這會更快,因爲您不會在堆上分配對象。

A&& operator*(A&& _lhs, const A& _rhs) { 
    std::cout << "Start Operator Move with: " << _lhs.name << " " << _rhs.name << std::endl; 
    _lhs.name += " "+_rhs.name; 
    return std::move(_lhs); 
} 

這不會導致內存泄漏,所以不是完全顯然是錯誤的,如第一個,但它仍然是錯誤的。如果你有一個臨時對象調用它返回到相同的臨時參考,但可能會導致懸掛引用:

A&& c = A("a") * A("b"); 

參考c必然受到A("a")創建的臨時但也不超出範圍的聲明的結尾。任何使用c的嘗試都有未定義的行爲。

兩個重載都應該返回值。

對於左側爲左值且右側爲右值的情況,您可能還需要重載,因爲這樣可以重新使用右側的對象。如果你補充說你還需要在兩個操作數都是右值的情況下重載。基本上,看看std::string如何定義operator+

+0

是的,第一個很明顯,我忘了將它改回來(請參閱我的編輯)。保存結果作爲移動參考確實是討厭的。 :/但這是這種結構的唯一缺陷嗎?即除非我嘗試保存結果的移動參考,我是否可以使用它? – Haatschii

+0

是的,但你爲什麼要編寫一個只有安全使用才安全的函數,否則它是微妙的未定義的?如果你正在爲你的類型實現移動語義,那麼推測移動是便宜的(否則不要打擾支持移動語義),所以只要'return _lhs;'按值返回是安全的並且將使用移動構造函數。返回一個懸而未決的參考就是玩火,將來有一天(也許你未來的自己)會詛咒你。 –

+0

是的......你最喜歡那個。它只是我真的很喜歡擺脫每次調用移動構造函數的「便宜」開銷。 :/ – Haatschii