C 11移动构造函数未调用,默认构造函数首选

C++11 move constructor not called, default constructor preferred

本文关键字:构造函数 默认 移动 调用      更新时间:2023-10-16

假设我们有此类:

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 :是否应该根据标准调用移动CTOR?如果是这样,为什么不打电话?

您所看到的是复制省略,它允许编译器将临时构造到目标中,然后将其复制/移动到目标中,从而将副本(或移动)构造函数/移动到破坏者对。C 11标准的第122.8.32条指定了允许编译器应用副本省略的情况:

当满足某些标准时,允许实施省略 即使复制/移动 对象的构造函数和/或破坏者具有侧面影响。在这样的 案例,实施对待省略的来源和目标 复制/移动操作仅为两种不同的参考方式 相同的对象以及该物体的破坏发生在后期 没有两个物体的时间将被摧毁 优化。复制/移动的这种省略 在以下 情况(可能会结合以消除多个副本):

  • 在具有类返回类型的函数的返回语句中,当表达式是具有
    的非挥发性自动对象的名称时 与函数返回类型相同的CV Unqualifelifel类型,
    可以通过构建自动
    来省略复制/移动操作 对象直接进入函数的返回值
  • 在抛出表达中,当操作数是非易失性自动对象的名称时,其范围不会延伸 最内在的封闭try-block(如果有的话)的结尾, 复制/移动操作从操作数到异常对象(15.1) 可以通过直接构造自动对象来省略 异常对象
  • 当尚未绑定到参考(12.2)的临时类对象将被复制/转移到类对象中 相同的cv unqualifelifel类型,可以省略复制/移动操作 将临时对象直接构建到
    的目标中 省略复制/移动
  • 当异常处理程序的异常解释(第15条)将同一类型的对象(CV-Qualifientation除外)为
    异常对象(15.1),可以省略复制/移动操作
    仔细处理例外情况作为异常的别名
    对象如果程序的含义将不变,则 执行由
    声明的对象的构造函数和破坏者 例外情况。

您在第三个代码行中获得的ctor输出是用于临时对象的构建。之后,实际上,临时性被移至新的变量z中。在这种情况下,编译器可能会选择使用复制/移动,这似乎就是这样做的。

标准状态:

(§12.8/31)当满足某些标准时,即使对象的副本/移动构造函数和/或破坏者具有侧面效应,允许实现省略类对象的复制/移动构造。[...]在以下情况下允许使用复制/移动操作(称为复制责任)(可以合并以消除多个副本):
[...]
- 当尚未绑定到参考的临时类对象(12.2)将被复制/移至具有相同CV unqualifel类型类型的类对象时,可以通过直接将临时对象构造到该类型中时,可以省略复制/移动操作省略的副本/移动的目标
[...]

一个重要的条件是源对象和目的地是相同类型的(除了CV合并,即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;
}

您正在调用 X's CC_7 constructor X("test")明确。

因此,它正在打印ctor

只是想评论说,如果您只想确保CTOR有效,则可以通过在条件下投掷来消除代码以消除编译器优化,例如:

X z( some_val > 1 ? X("test") : X("other test"));