![](/img/trans.png)
[英]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.