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

时间:2012-10-27 11:06:11

标签: c++ c++11 move-semantics

假设我们有这个课程:

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的情况吗?

:是否应该按照标准调用move-ctor?如果是这样,为什么不召唤?

3 个答案:

答案 0 :(得分:23)

你所看到的是copy elision,它允许编译器直接构造一个临时的目标,它将被复制/移入并因此忽略一个复制(或移动)构造函数/析构函数对。允许编译器应用复制省略的情况在C ++ 11标准的第12.8.32节中规定:

  

当满足某些条件时,允许省略实现   复制/移动类对象的构造,即使复制/移动   对象的构造函数和/或析构函数具有副作用。在这样的   在这种情况下,实现处理省略的源和目标   复制/移动操作只是两种不同的方式来指代   同一个对象,该对象的破坏发生在后期   两个物体在没有物体的情况下被摧毁的时间   优化。这种复制/移动的省略   以下允许称为复制省略的操作   情况(可能合并以消除多个副本):

     
      
  • 在具有类返回类型的函数的return语句中,当表达式是具有
    非易失性自动对象的名称时   与函数返回类型相同的cv-unquali fi ed类型   通过构造自动
    来省略复制/移动操作   直接将对象转换为函数的返回值
  •   
  • 在throw-expression中,当操作数是非范围自动对象的名称时,其范围不会超出范围   最里面的封闭try-block(如果有的话)的结尾,   从操作数复制/移动操作到异常对象(15.1)   可以通过直接构造自动对象来省略   异常对象
  •   
  • 当一个未绑定到引用(12.2)的临时类对象被复制/移动到一个类对象时   相同的cv-unquali fi ed类型,复制/移动操作可以省略   将临时对象直接构造到目标中   省略了复制/移动
  •   
  • 当异常处理程序的异常声明(第15条)声明一个相同类型的对象(cv-quali fi cation除外)作为
      异常对象(15.1),可以省略复制/移动操作   将异常声明作为例外的别名进行处理   对象,如果程序的含义将保持不变,除了   执行由
    声明的对象的构造函数和析构函数   例外声明。
  •   

答案 1 :(得分:3)

您在第三个代码行中获得的ctor输出用于构造临时对象。之后,确实将临时值移入新变量z。在这种情况下,编译器可能会选择忽略复制/移动,这似乎就是它所做的。

标准规定:

  

(§12.8/ 31)当满足某些条件时,允许实现省略类对象的复制/移动构造,即使对象的复制/移动构造函数和/或析构函数具有副作用。 [...]在下列情况下(允许合并以消除多份副本),允许复制/移动操作的省略,称为复制省略:   [...]
    - 当一个未绑定到引用(12.2)的临时类对象被复制/移动到具有相同cv-unquali fi ed类型的类对象时,可以通过将临时对象直接构造到该对象中来省略复制/移动操作。省略的复制/移动的目标
  [...]

一个重要条件是源对象和目标属于同一类型(除了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;
}

答案 2 :(得分:0)

您明确地致电X's char*构造函数X("test")

因此正在打印ctor