2013-07-02 48 views
8

它是調用者還是被調用者複製或移動函數的返回值?例如,如果我想實現一個隊列的POP()函數,這樣誰複製函數的返回值?

template <typename T> 
class queue 
{ 
    std::deque<T> d; 
public: 
    // ... // 
    T pop() 
    { 
     // Creates a variable whose destructor removes the first 
     // element of the queue if no exception is thrown. 
     auto guard = ScopeSuccessGuard([=]{ d.pop_front(); }); 
     return d.front(); 
    } 
} 

是我的守護範圍的析構函數複製前元素之後叫什麼?

編輯:後續問題:請問線

auto item = q.pop(); 

是強烈的異常安全的呢?

+1

是的,在返回指令(及其構造/副本)後,具有自動存儲持續時間的變量(如您的'guard')將被銷燬。您可以查看跳轉指令和聲明的標準部分6.6和6.7。也許更容易,你可以嘗試用虛擬對象打印一些東西在銷燬^^ – lip

回答

9

返回值在局部變量超出範圍之前被複製出來。複製/移動可能是到臨時位置(堆棧或寄存器)或直接到調用者自己的緩衝區或首選寄存器 - 這是優化/內聯問題。

如果涉及臨時位置,編譯器必須在調用者和被調用者之間安排一些工作分工,並且有許多針對返回值的OS和二進制對象/可執行格式特定約定(以及當然的函數參數),這樣使用一個編譯器編譯的庫/對象通常可以與另一個編譯器一起使用。

請問線...

auto item = q.pop(); 

...是強烈的異常安全嗎?

假設pop_front()不能throw,有趣的情況是該函數的返回之後返回一個臨時位置,從該值被再次複製到呼叫者緩衝器。在我看來,你沒有充分保護這一點。 Elision(被調用者直接構造調用者結果緩衝區/寄存器中的返回值)是允許的,但不是必需的。

爲了探討這個問題,我已經寫了下面的代碼:

#include <iostream> 

struct X 
{ 
    X() { std::cout << "X::X(this " << (void*)this << ")\n"; } 
    X(const X& rhs) { std::cout << "X::X(const X&, " << (void*)&rhs 
           << ", this " << (void*)this << ")\n"; } 
    ~X() { std::cout << "X::~X(this " << (void*)this << ")\n"; } 

    X& operator=(const X& rhs) 
    { std::cout << "X::operator=(const X& " << (void*)&rhs 
       << ", this " << (void*)this << ")\n"; return *this; } 
}; 

struct Y 
{ 
    Y() { std::cout << "Y::Y(this " << (void*)this << ")\n"; } 
    ~Y() { std::cout << "Y::~Y(this " << (void*)this << ")\n"; } 
}; 

X f() 
{ 
    Y y; 
    std::cout << "f() creating an X...\n"; 
    X x; 
    std::cout << "f() return x...\n"; 
    return x; 
}; 

int main() 
{ 
    std::cout << "creating X in main...\n"; 
    X x; 
    std::cout << "x = f(); main...\n"; 
    x = f(); 
} 

g++ -fno-elide-constructors編譯,我的輸出(額外評論)是:

creating X in main... 
X::X(this 0x22cd50) 
x = f(); main... 
Y::Y(this 0x22cc90) 
f() creating an X... 
X::X(this 0x22cc80) 
f() return x... 
X::X(const X&, 0x22cc80, this 0x22cd40) // copy-construct temporary 
X::~X(this 0x22cc80) // f-local x leaves scope 
Y::~Y(this 0x22cc90) 
X::operator=(const X& 0x22cd40, this 0x22cd50) // from temporary to main's x 
X::~X(this 0x22cd40) 
X::~X(this 0x22cd50) 

顯然,分配f()發生後左範圍:任何異常將在您的範圍警衛(此處由Y代表)被銷燬後。

同類的事情發生,如果主要包含X x = f();X x(f());,除了它的那是f() - 本地變量的破壞後調用拷貝構造函數。 (我明白一個編譯器的行爲有時是推理標準是否需要工作的一個很差的基礎,但是反過來說它更可靠:當它不工作時,編譯器的破壞 - 這是相對較少的 - 或者標準並不要求它。在這裏,編譯器行爲只是用來增加我對標準要求的印象。)

爲好奇繁瑣的細節:不是,它是典型的有代碼,只能以一種方式被稱爲是有用的,但一些可能安全是const X& x = f();,作爲const參考延長臨時的使用壽命,但我無法說服自己,標準要求有臨時的生命週期的延長臨時功能複製到沒有任何額外的副本;因爲它的價值很小 - 它在我的程序中「起作用」,並且有趣的是,如果隱藏返回值,臨時佔用相同的堆棧位置,這暗示f()代碼被有效編譯爲具有退出能力並且-f-no-elide-constructors選項不是那麼多禁用作爲一種優化方式來加入悲觀情緒:在調用函數之前爲臨時方法留下額外的堆棧空間,然後添加額外的代碼以從中複製並破壞臨時的然後重新調整堆棧指針....

6

返回值的副本由被調用者完成,並且必須在調用析構函數之前創建,否則無法返回本地構造的變量的值/內容。

下面是在標準的相關部分:第12.4節點11(析構函數)

析構函數隱含地調用

  • 用於與自動存儲持續時間構造的對象(3.7.3)當創建對象的塊退出(6.7)

我試圖找到一個地方,它是sa我認爲「回報發生在破壞之前」,但它並沒有像我想的那樣清楚地表明[除非我失去了某些東西]。