![](/img/trans.png)
[英]Why isn't move constructor elided whenever possible with `make_x()` functions?
[英]Why isn't the copy constructor elided here?
(我正在使用帶有-O2
gcc。)
取消副本構造函數似乎是一個直接的機會,因為訪問foo
的bar
副本中的字段的值沒有副作用。 但是復制構造函數被調用,因為我得到了輸出meep meep!
。
#include <iostream>
struct foo {
foo(): a(5) { }
foo(const foo& f): a(f.a) { std::cout << "meep meep!\n"; }
int a;
};
struct bar {
foo F() const { return f; }
foo f;
};
int main()
{
bar b;
int a = b.F().a;
return 0;
}
它不是12.8 / 15中描述的兩個復制權刪除法律案件:
返回值優化(從函數中返回自動變量,並通過直接在返回值中構造自動變量來避免將自動變量復制到返回值)-不。 f
不是自動變量。
臨時初始化程序(將臨時文件復制到對象,而不是構造臨時文件並將其復制,而是將臨時值直接構建到目標中)-nope f
也不是臨時文件。 bF()
是臨時的,但不會被復制到任何地方,它只訪問了一個數據成員,因此,當您離開F()
,沒有什么可以逃脫的。
由於復制蘋果的法律案例和將f
復制到F()
的返回值的法律案例均不會影響程序的可觀察行為,因此該標准禁止忽略該程序。 如果您將打印替換為某些不可觀察的活動,並檢查了程序集,則可能會發現此副本構造函數已被優化。 但這將是在“假設”規則下,而不是在復制構造函數省略規則下。
僅當確實不需要復制時才進行復制省略。 特別是在執行一個函數的過程中存在一個對象(稱為A),然后從第一個對象復制構造第二個對象(稱為B),然后緊接着是A。將被銷毀(即從函數退出時)。
在這種非常特殊的情況下,該標准允許編譯器將A和B合並為兩種引用同一對象的單獨方法。 無需創建A,然后從A復制副本B,然后銷毀A,它允許A和B被認為是引用同一對象的兩種方式,因此將(一個)對象創建為A,並且函數返回后開始被稱為B,但是即使副本構造函數有副作用,從A創建B的副本仍然可以跳過。 另外,請注意,在這種情況下,A(作為與B分開的對象)也不會被破壞-例如,如果您的dtor也有副作用,也可以(將)忽略它們。
您的代碼不適合該模式-在用於初始化第二個對象之后,第一個對象不會立即消失。 F()
返回后,有兩個對象實例。 在這種情況下,[命名]返回值優化(也稱為復制省略)根本不適用。
復制省略時的演示代碼:
#include <iostream>
struct foo {
foo(): a(5) { }
foo(const foo& f): a(f.a) { std::cout << "meep meep!\n"; }
int a;
};
int F() {
// RVO
std::cout << "F\n";
return foo();
}
int G() {
// NRVO
std::cout << "G\n";
foo x;
return x;
}
int main() {
foo a = F();
foo b = G();
return 0;
}
啟用優化功能后,MS VC ++和g ++均可從此代碼中優化兩個復制控制器。 即使關閉優化,g ++也會同時優化兩者。 關閉優化功能后,VC ++會優化匿名返回,但是將復制ctor用於命名返回。
之所以調用復制構造函數,是因為a)無法保證您無需修改就可以復制字段值,並且b)因為復制構造函數有副作用(打印消息)。
考慮臨時復制的一種更好的方法是使用臨時對象。 這就是標准的描述方式。 如果將臨時對象在即將銷毀之前復制到永久對象中,則可以將其“折疊”成永久對象。
在這里,您可以在函數return中構造一個臨時對象。 它實際上並沒有參與任何事情,因此您希望它被跳過。 但是如果你做了
b.F().a = 5;
如果刪除了副本,並且您對原始對象進行了操作,則可以通過非引用來修改b
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.