[英]error C4716: must return a value, thrown by function that actually does return a value
[英]How actually does a function return by value?
如果我有一個類A(按值返回一個對象),並且兩個函數f()和g()只有它們的返回變量不同:
class A
{
public:
A () { cout<<"constructor, "; }
A (const A& ) { cout<<"copy-constructor, "; }
A& operator = (const A& ) { cout<<"assignment, "; }
~A () { cout<<"destructor, "; }
};
const A f(A x)
{A y; cout<<"f, "; return y;}
const A g(A x)
{A y; cout<<"g, "; return x;}
main()
{
A a;
A b = f(a);
A c = g(a);
}
現在當我執行A b = f(a);
行時A b = f(a);
,它輸出:
copy-constructor, constructor, f, destructor
,假設f()中的對象y直接在目標處創建,即在對象b的內存位置創建,並且不涉及臨時值。
當我執行A c = g(a);
行時A c = g(a);
,它輸出:
copy-constructor, constructor, g, copy-constructor, destructor, destructor,
.
所以問題是為什么在g()cant的情況下,對象直接在c的內存位置創建,它在調用f()時發生的方式? 為什么在第二種情況下調用另一個復制構造函數(我認為是因為涉及臨時)?
問題是在第二種情況下,您將返回其中一個參數。 鑒於通常參數復制發生在調用者的站點,而不是函數內部(在本例中為main
),編譯器會生成副本,然后在進入g()
再次強制復制它。
來自http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/
其次,我還沒有找到一個在返回函數參數時會忽略副本的編譯器,就像我們的sorted實現一樣。 當你考慮如何完成這些約定時,它是有道理的:沒有某種形式的過程間優化,被排序的調用者不能知道最終將返回參數(而不是其他一些對象),因此編譯器必須在堆棧上為參數和返回值分配單獨的空間。
這是對您的代碼的一點修改,這將幫助您完全理解那里發生的事情:
class A{
public:
A(const char* cname) : name(cname){
std::cout << "constructing " << cname << std::endl;
}
~A(){
std::cout << "destructing " << name.c_str() << std::endl;
}
A(A const& a){
if (name.empty()) name = "*tmp copy*";
std::cout
<< "creating " << name.c_str()
<< " by copying " << a.name.c_str() << std::endl;
}
A& operator=(A const& a){
std::cout
<< "assignment ( "
<< name.c_str() << " = " << a.name.c_str()
<< " )"<< std::endl;
return *this;
}
std::string name;
};
這是這個類的用法:
const A f(A x){
std::cout
<< "// renaming " << x.name.c_str()
<< " to x in f()" << std::endl;
x.name = "x in f()";
A y("y in f()");
return y;
}
const A g(A x){
std::cout
<< "// renaming " << x.name.c_str()
<< " to x in f()" << std::endl;
x.name = "x in g()";
A y("y in g()");
return x;
}
int main(){
A a("a in main()");
std::cout << "- - - - - - calling f:" << std::endl;
A b = f(a);
b.name = "b in main()";
std::cout << "- - - - - - calling g:" << std::endl;
A c = g(a);
c.name = "c in main()";
std::cout << ">>> leaving the scope:" << std::endl;
return 0;
}
這是沒有任何優化編譯時的輸出:
constructing a in main()
- - - - - - calling f:
creating *tmp copy* by copying a in main()
// renaming *tmp copy* to x in f()
constructing y in f()
creating *tmp copy* by copying y in f()
destructing y in f()
destructing x in f()
- - - - - - calling g:
creating *tmp copy* by copying a in main()
// renaming *tmp copy* to x in f()
constructing y in g()
creating *tmp copy* by copying x in g()
destructing y in g()
destructing x in g()
>>> leaving the scope:
destructing c in main()
destructing b in main()
destructing a in main()
您發布的輸出是使用命名返回值優化編譯的程序的輸出。 在這種情況下,編譯器嘗試消除冗余的復制構造函數和析構函數調用 ,這意味着在返回對象時,它將嘗試返回對象而不創建它的冗余副本。 這是啟用NRVO的輸出:
constructing a in main()
- - - - - - calling f:
creating *tmp copy* by copying a in main()
// renaming *tmp copy* to x in f()
constructing y in f()
destructing x in f()
- - - - - - calling g:
creating *tmp copy* by copying a in main()
// renaming *tmp copy* to x in f()
constructing y in g()
creating *tmp copy* by copying x in g()
destructing y in g()
destructing x in g()
>>> leaving the scope:
destructing c in main()
destructing b in main()
destructing a in main()
在第一種情況下,由於NRVO已完成其工作,因此不會y in f()
復制y in f()
創建*tmp copy*
。 在第二種情況下,雖然無法應用NRVO,因為已在此函數中聲明了返回槽的另一個候選者。 有關更多信息,請參閱: C ++:使用“return”語句避免復制 :)
它可以(幾乎)優化整個g()函數調用,在這種情況下,您的代碼如下所示:
A a;
A c = a;
實際上這就是你的代碼正在做的事情。 現在,當您傳遞a
作為按值參數(即不是引用)時,編譯器幾乎必須在那里執行復制,然后它按值返回此參數,它必須執行另一個副本。
在f()的情況下,因為它將實際上是臨時的,返回到未初始化的變量,編譯器可以看到使用c
作為f()內部變量的存儲是安全的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.