[英]C++ universal reference in constructor and return value optimization (rvo)
為什么在帶有通用引用參數的構造函數的類中不會出現右值優化?
http://coliru.stacked-crooked.com/a/672f10c129fe29a0
#include <iostream>
template<class ...ArgsIn>
struct C {
template<class ...Args>
C(Args&& ... args) {std::cout << "Ctr\n";} // rvo occurs without &&
~C(){std::cout << "Dstr\n";}
};
template<class ...Args>
auto f(Args ... args) {
int i = 1;
return C<>(i, i, i);
}
int main() {
auto obj = f();
}
輸出:
Ctr
Ctr
Dstr
Ctr
Dstr
Dstr
我認為問題在於實例化
template<class ...Args>
C(Args&& ... args) {std::cout << "Ctr\n";}
就語言而言,不是復制/移動構造函數,因此編譯器不能忽略對它們的調用。 從§12.8[class.copy] / p2-3開始,增加了重點並省略了示例:
如果
X
類的第一個參數是X&
,const X&
,volatile X&
或const volatile X&
,並且沒有其他參數或者所有其他參數都有默認參數,則類X
非模板構造函數是一個復制構造函數(8.3.6) )。如果
X
類的第一個參數的類型為X&&
,const X&&
,volatile X&&
或const volatile X&&
,並且沒有其他參數,或者所有其他參數都有默認參數(8.3),則const X&&
類的非模板構造函數是一個移動構造const X&&
。 6)。
換句話說,作為模板的構造函數永遠不能是復制或移動構造函數。
返回值優化是copy elision的一個特例,描述為(§12.8[class.copy] / p31):
當滿足某些條件時,允許實現省略類對象的復制/移動構造,即使為復制/移動操作選擇的構造函數和/或對象的析構函數具有副作用。
這允許實現省略“復制/移動構造”; 使用既不是復制構造函數也不是移動構造函數的東西構造對象不是“復制/移動構造”。
因為C
具有用戶定義的析構函數,所以不會生成隱式移動構造函數。 因此,重載決策將選擇模板化的構造函數,其中Args
推導為C
,這比rvalues的隱式復制構造函數更好。 但是,編譯器不能忽略對此構造函數的調用,因為它具有副作用,既不是復制構造函數也不是移動構造函數。
如果是模板化的構造函數
template<class ...Args>
C(Args ... args) {std::cout << "Ctr\n";}
然后它不能用Args
= C
實例化以生成復制構造函數,因為這將導致無限遞歸。 標准中有一條特殊規則禁止此類構造函數和實例化(§12.8[class.copy] / p6):
如果類
X
的構造函數的第一個參數是類型(可選地是cv-qualified)X
並且沒有其他參數或者所有其他參數都具有默認參數,那么它的構造函數的聲明是錯誤的。 從不實例化成員函數模板以生成此類構造函數簽名。
因此,在這種情況下,唯一可行的構造函數將是隱式定義的復制構造函數,並且可以省略對該構造函數的調用。
如果我們改為從C
刪除自定義析構函數,並添加另一個類來跟蹤何時調用C
的析構函數:
struct D {
~D() { std::cout << "D's Dstr\n"; }
};
template<class ...ArgsIn>
struct C {
template<class ...Args>
C(Args&& ... args) {std::cout << "Ctr\n";}
D d;
};
我們只看到對D
的析構函數的一次調用,表明只構造了一個C
對象。 這里C
的移動構造函數是通過重載解析隱式生成和選擇的,並且您再次看到RVO啟動。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.