2011-08-23 21 views
7

閱讀this有關從函數返回右值引用的答案讓我想到,我怎樣才能在C++ 0x中編寫id函數。在C++中的「id」函數0x

基本上,我想id是一個無功功能,一個沒有可觀察到的程序效果的功能。

我第一次嘗試如下:

#include <iostream> 

class X 
{ 
public: 
    X(std::string&& s) : s(std::move(s)) {}; 
    X(const std::string& s) : s(s) {}; 
    std::string s; 
    ~X() { std::cout << "Destroying: " << s << std::endl; } 
private: 
    X(const X&) {}; 
    X(X&&) {}; 
}; 

template <class T> 
T&& id(T&& x) { return static_cast<T&&>(x); } 

int main() 
{ 
    auto&& x1 = X("x1"); 
    std::cout << "Line 1" << std::endl; 
    auto&& x2 = id(X("x2")); 
    std::cout << "Line 2" << std::endl; 
} 

但是,我擔心在這種情況下,X2是懸空的參考,爲X("x2")被毀滅之前「2號線」執行。

所以在這裏,很清楚id有一個可觀察到的效果。

如何在C++ 0x中編寫id函數,該函數特別適用於不帶移動/複製構造函數的類型。

+0

問題是函數聲明並不傳達返回的引用是否仍然引用傳遞給它的同一個對象。強制編譯器檢查函數的實現是不合理的,以便確定它返回的是什麼,並使臨時的生命週期取決於此分析。 – sellibitze

回答

7

你不行。通常,您不應該編寫返回右值引用的函數,正如您正確指出的那樣,不能將該臨時的生命週期延長足夠長的時間。

0

你想要做什麼叫做完美轉發,並且在,做它的STL的函數:

template <class T> T&& forward(typename remove_reference<T>::type& t) noexcept 
{ 
    return static_cast<T&&>(t) 
} 
template <class T> T&& forward(typename remove_reference<T>::type&& t) noexcept 
{ 
    return static_cast<T&&>(t) 
} 

您需要remove_reference避免參考崩潰。而且使用它時,你必須指定你想轉發的對象類型:

std::forward<X>(X("x2")); 
+1

我相信這是一箇舊的規格,而不是目前的標準。另外,它不*解決問題。 – Puppy

0

編程語言中的大多數事情並不完全和完全免費的。除非你編寫僅編譯時代碼,否則編寫身份函數不太可能是免費的。

讓我們返工你的代碼位:

#include <algorithm> 
#include <iostream> 

template <typename T> 
T id1(T&& t) 
{ 
    return t; 
} 

template <typename T> 
T id2(T&& t) 
{ 
    return std::move(t); 
} 


class X 
{ 
public: 
    X() 
    { output0("Xdef"); } 
    X(std::string const& s) : label_(s) 
    { output1("Xstr",s); } 
    X(X const& x) : label_(x.label_) 
    { output1("Xcopy", x); } 
    X(X&& x) : label_(std::move(x.label_)) 
    { output1("Xmove", x); } 

    X& operator =(X const& x) 
    { 
    output1("operator =copy", x); 
    label_ = x.label_; 
    return *this; 
    } 

    X& operator =(X&& x) 
    { 
    using std::swap; 
    output1("operator =move", x); 
    swap(label_, x.label_); 
    return *this; 
    } 

    ~X() 
    { output0("~X"); } 

private: 
    void output_id() const 
    { 
    std::cout << this << '[' << label_ << "]"; 
    } 

    void output0(std::string const& name) const 
    { 
    output_id(); 
    std::cout << ": " << name << "()" << std::endl; 
    } 

    void output1(std::string const& name, std::string const& str) const 
    { 
    output_id(); 
    std::cout 
     << ": " << name 
     << "(\"" << str 
     << "\")" << std::endl; 
    } 

    void output1(std::string const& name, X const& arg) const 
    { 
    output_id(); 
    std::cout << ": " << name << '('; 
    arg.output_id(); 
    std::cout << ')' << std::endl; 
    } 

    std::string label_; 
}; 

int main() 
{ 
    { 
    std::cout << "CASE A:\n"; 
    auto x = X("x1"); 
    } 
    std::cout << "\n"; 
    { 
    std::cout << "CASE B:\n"; 
    auto x = id1(X("x2")); 
    } 
    std::cout << "\n"; 
    { 
    std::cout << "CASE C:\n"; 
    auto x = id2(X("x3")); 
    } 
    std::cout << "\n"; 
    { 
    std::cout << "CASE D:\n"; 
    X x = id1(X("x4")); 
    } 
    std::cout << "\n"; 
    { 
    std::cout << "CASE E:\n"; 
    X x = id2(X("x5")); 
    } 
}  

並運行時,它輸出(使用GCC V4.8快照):

$ ./a.out 
CASE A: 
0x7fff411fc530[x1]: Xstr("x1") 
0x7fff411fc530[x1]: ~X() 

CASE B: 
0x7fff411fc540[x2]: Xstr("x2") 
0x7fff411fc520[x2]: Xcopy(0x7fff411fc540[x2]) 
0x7fff411fc540[x2]: ~X() 
0x7fff411fc520[x2]: ~X() 

CASE C: 
0x7fff411fc540[x3]: Xstr("x3") 
0x7fff411fc520[x3]: Xmove(0x7fff411fc540[]) 
0x7fff411fc540[]: ~X() 
0x7fff411fc520[x3]: ~X() 

CASE D: 
0x7fff411fc540[x4]: Xstr("x4") 
0x7fff411fc520[x4]: Xcopy(0x7fff411fc540[x4]) 
0x7fff411fc540[x4]: ~X() 
0x7fff411fc520[x4]: ~X() 

CASE E: 
0x7fff411fc540[x5]: Xstr("x5") 
0x7fff411fc520[x5]: Xmove(0x7fff411fc540[]) 
0x7fff411fc540[]: ~X() 
0x7fff411fc520[x5]: ~X() 
$ 

情況A只是調用構造函數對於X. =在這種情況下相當於將=的右側傳遞給X,即它不是一個賦值。

情況B調用id1()它不移動它的返回參數。由於返回的值沒有在id()的調用堆棧上定義,並且該值是一個左值(包含右值),所以它不會在返回時自動移動並因此被複制。

情況C調用id2(),它在返回時調用移動構造函數。

病例D和E分別與病例B和病例C相同,只是如果您對此持懷疑態度,則不會使用auto

在最壞的情況下,移動應該被看作是優化的副本,並且與副本一樣糟糕(儘管它們往往會好得多)。即使是最佳的移動也會有成本(例如,將一些數據(通常)從一個堆棧幀複製到另一個堆棧幀)。在運行時代碼中完全避免複製/移動的唯一方法是返回值優化和複製ellison符合編譯器使用的條件。