提问人:emesx 提问时间:10/27/2012 更新时间:6/26/2023 访问量:9566
未调用 C++11 移动构造函数,默认构造函数首选
C++11 move constructor not called, default constructor preferred
问:
假设我们有这个类:
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。我希望最后一个对象 () 是移动构造的,但事实并非如此!如果我使用,那么输出的最后一行是 ,正如我所期望的那样。是 (N)RVO 的情况吗?z
X z( move(X("test")) );
ctor move test
问:是否应该按照标准调用移动器?如果是这样,为什么不叫它?
答:
您看到的是复制省略,它允许编译器直接将临时构造到要复制/移动到的目标中,从而省略复制(或移动)构造函数/析构函数对。允许编译器应用复制省略的情况在 C++11 标准的 §12.8.32 中指定:
当满足某些条件时,允许实现省略 类对象的复制/移动构造,即使复制/移动 对象的构造函数和/或析构函数具有副作用。在这样的 情况下,实现处理省略的来源和目标 复制/移动操作只是引用 同一对象,并且该对象的破坏发生在较晚的 如果没有 优化。这种复制/移动的省略 以下操作中允许执行称为复制省略的操作 情况(可以组合起来消除多个副本):
- 在具有类返回类型的函数的 return 语句中,当表达式是与函数返回类型具有相同 cv 非限定类型的非易失性自动对象的名称时,可以通过将自动
对象
直接构造到函数的返回值中来省略
复制/移动操作- 在 throw-expression 中,当操作数是非易失性自动对象的名称时,其范围不会超出 最内层封闭的 try-block(如果有)的末端, 将操作从操作数复制/移动到异常对象 (15.1) 可以通过将自动对象直接构造到 异常对象
- 当未绑定到引用的临时类对象 (12.2) 将被复制/移动到具有 HE 的类对象时 相同的 cv-unqualified 类型,复制/移动操作可以通过以下方式省略 将临时对象直接构造到
省略的复制/移动的目标中- 当异常处理程序的异常声明(第 15 条)声明与异常对象 (15.1) 类型相同的对象(cv-qualification 除外)
时,可以通过将异常声明视为异常
对象的别名
来省略复制/移动操作,如果程序的含义保持不变,但 执行 exception-declaration 声明的对象的
构造函数和析构函数。
评论
std::move
X z((rand() % 2) ? X("test") : X("TEST"));
return local_var;
local_var
您正在显式调用构造函数。X's
char*
X("test")
因此,它是印刷ctor
评论
X
z
您在第三行代码中得到的输出是用于构造临时对象的。在那之后,实际上,临时变量被移动到新变量中。在这种情况下,编译器可能会选择省略复制/移动,似乎这就是它所做的。ctor
z
该标准规定:
(第12.8/31节)当满足某些条件时,允许实现省略类对象的复制/移动构造,即使该对象的复制/移动构造函数和/或析构函数具有副作用。[...]在以下情况下,允许这种复制/移动操作的省略,称为复制省略(可以组合以消除多个副本):
[...]
- 当未绑定到引用 (12.2) 的临时类对象将被复制/移动到具有相同 cv 非限定类型的类对象时,可以通过将临时对象直接构造到省略的复制/移动的目标中来省略复制/移动
操作 [...]
一个重要的条件是源对象和目标属于同一类型(除了 cv-qualification,即 )。const
因此,可以强制调用移动构造函数的一种方法是将对象初始化与隐式类型转换相结合:
#include <iostream>
struct B
{};
struct A
{
A() {}
A(A&& a) {
std::cout << "move" << std::endl;
}
A(B&& b) {
std::cout << "move from B" << std::endl;
}
};
int main()
{
A a1 = A(); // move elided
A a2 = B(); // move not elided because of type conversion
return 0;
}
评论
A::A(B&& b)
只是想评论一下,如果你只想确保移动 ctor 工作,你可以破解代码,通过抛出一个条件来消除编译器优化,例如:
X z( some_val > 1 ? X("test") : X("other test"));
下一个:模板副本分配功能的混淆
评论
char *
-fno-elide-constructors