假设我们有这个类:
class X {
public:
explicit X (char* c) { cout<<"ctor"<<endl; init(c); };
X (X& lv) { cout<<"copy"<<endl; init(lv.c_); };
X (X&& rv) { cout<<"move"<<endl; c_ = rv.c_; rv.c_ = nullptr; };
const char* c() { return c_; };
private:
void init(char *c) { c_ = new char[strlen(c)+1]; strcpy(c_, c); };
char* c_;
};
示例用法:
X x("test");
cout << x.c() << endl;
X y(x);
cout << y.c() << endl;
X z( X("test") );
cout << z.c() << endl;
输出为:
ctor
test
copy
test
ctor <-- why not move?
test
我使用的是默认设置的VS2010。我希望最后一个对象(z
)是移动构造的,但它不是!如果我使用X z( move(X("test")) );
,那么输出的最后一行是ctor move test
,正如我所期望的那样。是否为(N)RVO病例?
Q:是否应该按照标准调用move-actor?如果是这样,为什么不叫它?
4条答案
按热度按时间llew8vvj1#
你看到的是copy elision,它允许编译器直接将一个临时对象构造成它要复制/移动到的目标,从而省略了复制(或移动)构造函数/析构函数对。允许编译器应用复制省略的情况在C++11标准的§12.8.32中指定:
当满足某些条件时,允许实现省略类对象的复制/移动构造,即使对象的复制/移动构造函数和/或析构函数具有副作用。在这种情况下,实现将省略的复制/移动操作的源和目标简单地视为引用同一对象的两种不同方式,并且该对象的销毁发生在两个对象在没有优化的情况下被销毁的时间的较晚时间。这种复制/移动操作的省略(称为复制省略)在以下情况下是允许的(可以组合以消除多个副本):
与函数返回类型相同的艾德类型,
复制/移动操作可以通过构造自动
对象直接转换为函数的返回值
省略复制/移动
异常对象(15.1),则可以省略复制/移动操作
通过将异常声明视为异常的别名
声明的对象的构造函数和析构函数执行之外,程序的含义将保持不变
异常声明。
8gsdolmq2#
在第三个代码行中得到的
ctor
输出用于构造临时对象。在此之后,实际上,临时变量被移动到新变量z
中。在这种情况下,编译器可能会选择省略复制/移动,看起来它就是这么做的。该标准规定:
(§12.8/31)当满足某些条件时,允许实现省略类对象的复制/移动构造,即使对象的复制/移动构造函数和/或析构函数有副作用。[...]这种复制/移动操作的省略,称为复制省略,在以下情况下是允许的(可以组合以消除多个副本):
[...]
[...]
一个重要的条件是源对象和目标对象的类型相同(除了cv限定,即
const
)。因此,可以强制调用move构造函数的一种方法是将对象初始化与 * 隐式类型转换结合起来:*
8tntrjer3#
您正在显式调用
X's
char*
构造函数X("test")
。因此打印
ctor
t0ybt7op4#
只是想评论一下,如果你只想确保移动ctor工作,你可以通过抛出一个条件来破解代码以消除编译器优化,例如: