最近,我一直在阅读this post和 that post建议停止返回 const 对象。 此建议也由 Stephan T. Lavavej 在 his talk 中给出在 2013 年的 Going Native 中。
我写了一个非常简单的测试来帮助我理解在所有这些情况下调用了哪个构造函数/运算符:
- 返回 const 或非 const 对象
- 如果启动返回值优化 (RVO) 会怎样?
- 如果启用命名返回值优化 (NRVO) 会怎样?
这是测试:
#include <iostream>
void println(const std::string&s){
try{std::cout<<s<<std::endl;}
catch(...){}}
class A{
public:
int m;
A():m(0){println(" Default Constructor");}
A(const A&a):m(a.m){println(" Copy Constructor");}
A(A&&a):m(a.m){println(" Move Constructor");}
const A&operator=(const A&a){m=a.m;println(" Copy Operator");return*this;}
const A&operator=(A&&a){m=a.m;println(" Move Operator");return*this;}
~A(){println(" Destructor");}
};
A nrvo(){
A nrvo;
nrvo.m=17;
return nrvo;}
const A cnrvo(){
A nrvo;
nrvo.m=17;
return nrvo;}
A rvo(){
return A();}
const A crvo(){
return A();}
A sum(const A&l,const A&r){
if(l.m==0){return r;}
if(r.m==0){return l;}
A sum;
sum.m=l.m+r.m;
return sum;}
const A csum(const A&l,const A&r){
if(l.m==0){return r;}
if(r.m==0){return l;}
A sum;
sum.m=l.m+r.m;
return sum;}
int main(){
println("build a");A a;a.m=12;
println("build b");A b;b.m=5;
println("Constructor nrvo");A anrvo=nrvo();
println("Constructor cnrvo");A acnrvo=cnrvo();
println("Constructor rvo");A arvo=rvo();
println("Constructor crvo");A acrvo=crvo();
println("Constructor sum");A asum=sum(a,b);
println("Constructor csum");A acsum=csum(a,b);
println("Affectation nrvo");a=nrvo();
println("Affectation cnrvo");a=cnrvo();
println("Affectation rvo");a=rvo();
println("Affectation crvo");a=crvo();
println("Affectation sum");a=sum(a,b);
println("Affectation csum");a=csum(a,b);
println("Done");
return 0;
}
这是 Release模式下的输出(使用 NRVO 和 RVO):
build a
Default Constructor
build b
Default Constructor
Constructor nrvo
Default Constructor
Constructor cnrvo
Default Constructor
Constructor rvo
Default Constructor
Constructor crvo
Default Constructor
Constructor sum
Default Constructor
Move Constructor
Destructor
Constructor csum
Default Constructor
Move Constructor
Destructor
Affectation nrvo
Default Constructor
Move Operator
Destructor
Affectation cnrvo
Default Constructor
Copy Operator
Destructor
Affectation rvo
Default Constructor
Move Operator
Destructor
Affectation crvo
Default Constructor
Copy Operator
Destructor
Affectation sum
Copy Constructor
Move Operator
Destructor
Affectation csum
Default Constructor
Move Constructor
Destructor
Copy Operator
Destructor
Done
Destructor
Destructor
Destructor
Destructor
Destructor
Destructor
Destructor
Destructor
我不明白的是: 为什么在“Constructor csum”测试中使用移动构造函数?
返回对象是 const 所以我真的觉得它应该调用复制构造函数。
我在这里错过了什么?
这应该不是编译器的错误,Visual Studio 和 clang 都给出相同的输出。
最佳答案
What I don't understand is this: why is the move constructor used in the "Constructor csum" test ?
在这种特殊情况下,允许编译器执行 [N]RVO,但它没有执行。第二好的事情是移动构造返回的对象。
The return object is const so I really feel like it should call the copy constructor.
这根本不重要。但我想这并不完全显而易见,所以让我们来看看返回值在概念上意味着什么,以及 [N]RVO 是什么。为此,最简单的方法是忽略返回的对象:
T f() {
T obj;
return obj; // [1] Alternatively: return T();
}
void g() {
f(); // ignore the value
}
在标记为 [1] 的行中,存在从本地/临时对象到返回值的拷贝。 即使该值被完全忽略。这就是您在上面的代码中练习的内容。
如果不忽略返回值,如:
T t = f();
从概念上讲,从返回值到 t
局部变量存在第二个拷贝。在您的所有案例中都删除了第二个拷贝。
对于第一个拷贝,返回的对象是否为const
并不重要,编译器根据[conceptual copy/move]构造函数的参数来决定做什么,而不是是否正在构造的对象将是 const
或不是。这与:
// a is convertible to T somehow
const T ct(a);
T t(a);
目标对象是否为 const 并不重要,编译器需要根据参数而不是目标找到最佳构造函数。
现在,如果我们把它带回到你的练习中,为了确保没有调用复制构造函数,你需要修改 return
语句的参数:
A force_copy(const A&l,const A&r){ // A need not be `const`
if(l.m==0){return r;}
if(r.m==0){return l;}
const A sum;
return sum;
}
这应该会触发复制构造,但是再一次,它足够简单,如果编译器认为合适,它可能会完全删除该拷贝。
关于c++ - 如何移动一个 const 返回的对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20545014/