如果我有一个类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);行时,它输出:
copy-constructor, constructor, f, destructor,假设f()中的对象y是直接在目的地创建的,即在对象b的内存位置创建的,并且不涉及时间。
而当我执行A c = g(a);行时,它输出:
copy-constructor, constructor, g, copy-constructor, destructor, destructor,。
所以问题是,为什么在g()的情况下,对象不能直接在c的内存位置创建,就像调用f()时发生的那样?为什么它在第二种情况下调用一个额外的复制构造函数(我认为这是因为临时的参与)?
发布于 2012-06-06 21:55:22
不同之处在于,在g的情况下,您将返回一个传递给函数的值。该标准明确规定了在哪些条件下可以在12.8p31中省略副本,并且不包括从函数参数中省略副本。
基本上,问题是参数和返回对象的位置由调用约定固定,编译器不能基于实现(甚至可能在调用位置不可见)返回参数这一事实来更改调用约定。
我在一段时间前开始了一个短暂的博客(我希望有更多的时间...)我写了几篇关于NRVO和复制省略的文章,可能有助于澄清这一点(谁知道呢:):
Value semantics: NRVO
Value semantics: Copy elision
发布于 2012-06-06 21:26:36
问题是,在第二种情况下,您返回的是其中一个参数。鉴于参数复制通常发生在调用者的位置,而不是在函数内(本例中为main),编译器会进行复制,然后一旦进入g(),就会被强制再次复制。
来自http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/
第二,我还没有找到一个编译器,它可以在函数参数返回时省略副本,就像我们的
实现一样。当您考虑这些省略是如何完成的时,这是有意义的:如果没有某种形式的过程间优化,sorted的调用者无法知道参数(而不是其他对象)最终将被返回,因此编译器必须在堆栈上为参数和返回值分配单独的空间。
发布于 2012-06-06 21:37:47
下面是对您的代码的一小段修改,它将帮助您完全理解那里发生了什么:
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()您发布的输出是用Named Return Value Optimization编译的程序的输出。在这种情况下,编译器试图消除冗余的复制构造函数和析构函数调用,这意味着当返回对象时,它将尝试在不创建对象的冗余副本的情况下返回对象。以下是启用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()来创建*tmp copy*。然而,在第二种情况下,不能应用NRVO,因为在此函数中声明了返回槽的另一个候选者。有关更多信息,请参阅:C++ : Avoiding copy with the "return" statement :)
https://stackoverflow.com/questions/10914633
复制相似问题