簡體   English   中英

為什么三元運算符會阻止返回值優化?

[英]Why does the ternary operator prevent Return-Value Optimization?

為什么三元運算符會阻止 MSVC 中的返回值優化 (RVO)? 考慮以下完整的示例程序:

#include <iostream>

struct Example
{
    Example(int) {}
    Example(Example const &) { std::cout << "copy\n"; }
};

Example FunctionUsingIf(int i)
{
    if (i == 1)
        return Example(1);
    else
        return Example(2);
}

Example FunctionUsingTernaryOperator(int i)
{
    return (i == 1) ? Example(1) : Example(2);
}

int main()
{
    std::cout << "using if:\n";
    Example obj1 = FunctionUsingIf(0);
    std::cout << "using ternary operator:\n";
    Example obj2 = FunctionUsingTernaryOperator(0);
}

用 VC 2013 像這樣編譯: cl /nologo /EHsc /Za /W4 /O2 stackoverflow.cpp

輸出:

using if:
using ternary operator:
copy

顯然,三元運算符以某種方式阻止了 RVO。 為什么? 為什么編譯器不夠聰明,無法看到使用三元運算符的函數與使用 if 語句的函數執行相同的操作,並相應地進行優化?

查看程序輸出,在我看來,編譯器確實在這兩種情況下都忽略了,為什么?

因為,如果沒有激活 elide,正確的輸出將是:

  1. 在函數返回時構造示例對象;
  2. 將其復制到臨時;
  3. 將臨時文件復制到 main 函數中定義的對象。

所以,我希望在我的屏幕上至少有 2 個“復制”輸出。 事實上,如果我執行你的程序,用 g++ 編譯,用 -fno-elide-constructor,我從每個函數中得到 2 個復制消息。

有趣的是,如果我對 clang 做同樣的事情,當函數FunctionUsingTernaryOperator(0);時,我會收到 3 個“復制”消息FunctionUsingTernaryOperator(0); 被調用,我猜這是由於編譯器如何實現三元。 我猜它正在生成一個臨時來解決三元運算符並將這個臨時復制到 return 語句。

這個相關問題包含答案。

標准規定何時在 return 語句中允許復制或移動省略:(12.8.31)

  • 在具有類返回類型的函數中的 return 語句中,當表達式是與函數返回類型具有相同 cvunqualified 類型的非易失性自動對象(函數或 catch-clause 參數除外)的名稱時,副本/move 操作可以通過將自動對象直接構造到函數的返回值中來省略
  • 當尚未綁定到引用(12.2)的臨時類對象將被復制/移動到具有相同 cv-unqualified 類型的類對象時,可以通過將臨時對象直接構造到目標中來省略復制/移動操作省略的復制/移動

所以基本上復制省略只發生在以下情況:

  1. 返回一個命名對象。
  2. 返回一個臨時對象

如果您的表達式不是命名對象或臨時對象,則回退到復制。

一些有趣的行為:

  • return (name); 不會阻止復制省略(請參閱此問題
  • return true?name:name; 應該防止復制省略,但 gcc 4.6 至少在這個上是錯誤的(參見這個問題

編輯:

我在上面留下了我的原始答案,但 Christian Hackl 在他的評論中是正確的,它沒有回答問題。

就規則而言,示例中的三元運算符產生一個臨時對象,因此 12.8.31 允許省略復制/移動。 所以從C++語言的角度來看。 當從 FunctionUsingTernaryOperator 返回時,完全允許編譯器省略副本。

現在顯然省略沒有完成。 我想唯一的原因是 Visual Studio 編譯器團隊還沒有實現它。 因為理論上他們可以,也許在未來的版本中他們會。

我可以看到它違反了關於 RVO 的一個一般規則——返回對象(應該)被定義在一個位置。

下面的代碼段滿足規則:

Example e;
e = (i == 1)? Example{1} : Example{2};
return e;

但是在下面的原始表達式中,根據 MSVC 在兩個不同的位置定義了兩個Example對象:

return (i == 1) ? Example(1) : Example(2);

雖然這兩個片段之間的轉換對人類來說是微不足道的,但我可以想象,如果沒有專門的實現,它不會在編譯器中自動發生。 換句話說,這是一個技術上支持 RVO 但開發人員沒有意識到的極端情況。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM