简体   繁体   English

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

[英]C++11 move constructor not called, default constructor preferred

Suppose we have this class: 假设我们有这个类:

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_;

};

and this sample usage: 和此示例用法:

X x("test");
cout << x.c() << endl;
X y(x);
cout << y.c() << endl;
X z( X("test") );
cout << z.c() << endl;

The output is: 输出为:

ctor
test
copy
test
ctor   <-- why not move?
test

I am using VS2010 with default settings. 我使用默认设置的VS2010。 I'd expect the last object ( z ) to be move-constructed, but it's not! 我希望最后一个对象( z )是移动构造的,但事实并非如此! If I use X z( move(X("test")) ); 如果我使用X z( move(X("test")) ); then the last lines of the output are ctor move test , as I'd expect. 然后输出的最后ctor move test行是ctor move test ,正如我所期望的那样。 Is it a case of (N)RVO? 是(N)RVO吗?

Q : Should the move-ctor be called according to the standard? :是否应该根据标准调用动轴? If so, why isn't it called? 如果是这样,为什么不调用它呢?

What you are seeing is copy elision , which allows the compiler to directly construct a temporary into a target it is to be copied/moved into and thus elide a copy (or move) constructor/destructor pair. 您将看到的是复制省略 ,它允许编译器直接将临时结构构造到要复制/移动到的目标中,从而消除复制(或移动)构造函数/析构函数对。 The situations in which the compiler is allowed to apply copy elision are specified in §12.8.32 of the C++11 standard: C ++ 11标准的第12.8.32节指定了允许编译器应用复制省略的情况:

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects. 当满足某些条件时,即使该对象的复制/移动构造函数和/或析构函数具有副作用,也允许实现忽略类对象的复制/移动构造。 In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization. 在这种情况下,实现将忽略的复制/移动操作的源和目标视为引用同一对象的两种不同方式,并且该对象的销毁发生在两个对象本来应该以较晚的时间发生。没有优化就销毁。 This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which maybe combined to eliminate multiple copies): 在以下情况下允许进行复制/移动操作的这种删减(称为复制删减)(可以合并以消除多个副本):

  • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object with 在具有类返回类型的函数的return语句中,当表达式是具有以下内容的非易失性自动对象的名称时:
    the same cv-unqualified type as the function return type, the 与函数返回类型相同的cv不合格类型,
    copy/move operation can be omitted by constructing the automatic 复制/移动操作可以通过构造自动
    object directly into the function's return value 对象直接进入函数的返回值
  • in a throw-expression, when the operand is the name of a non-volatile automatic object whose scope does not extend beyond the end of the innermost enclosing try-block (if there is one), the copy/move operation from the operand to the exception object (15.1) can be omitted by constructing the automatic object directly into the exception object 在throw-expression中,当操作数是非易失性自动对象的名称时,其范围不会扩展到最里面的封闭try-block的末尾(如果有),则将操作数的复制/移动操作复制到通过将自动对象直接构造到异常对象中,可以省略异常对象(15.1)
  • when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with he same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the 当尚未绑定到引用(12.2)的临时类对象将被复制/移动到具有相同cv不限定类型的类对象时,可以通过将临时对象直接构造到目标中来省略复制/移动操作的
    omitted copy/move 省略的复制/移动
  • when the exception-declaration of an exception handler (Clause 15) declares an object of the same type (except for cv-qualification) as 当异常处理程序的异常声明(第15条)声明与以下对象具有相同类型(cv限定除外)的对象时:
    the exception object (15.1), the copy/move operation can be omitted 异常对象(15.1),可以省略复制/移动操作
    bytreatingthe exception-declaration as an alias for the exception 通过将异常声明作为异常的别名进行处理
    object if the meaning of the program will be unchanged except for the execution of constructors and destructors for the object declared by 程序的含义将保持不变的对象,但对于由声明的对象执行的构造函数和析构函数除外
    the exception-declaration. 异常声明。

The ctor output you get in your third code line is for the construction of the temporary object. 您在第三行代码中获得的ctor输出用于构造临时对象。 After that, indeed, the temporary is moved into the new variable z . 之后,确实将临时变量移到新变量z In such a situation the compiler may choose to elide the copy/move, and it seems that is what it did. 在这种情况下,编译器可能会选择取消复制/移动,这似乎就是这样做的。

The Standard states: 该标准规定:

(§12.8/31) When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects. (第12.8 / 31节)如果满足某些条件,则允许实现忽略类对象的复制/移动构造,即使该对象的复制/移动构造函数和/或析构函数具有副作用。 [...] This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies): [...]在以下情况下允许复制/移动操作的这种省略,称为复制省略(可以合并以消除多个副本):
[...] [...]
- when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move -当尚未绑定到引用(12.2)的临时类对象将被复制/移动到具有相同cv不合格类型的类对象时,可以通过将临时对象直接构造为以下形式来省略复制/移动操作:省略复制/移动的目标
[...] [...]

One important condition is that the source object and the destination are of the same type (apart from cv-qualification, ie things like const ). 一个重要条件是源对象和目标对象具有相同的类型(除了cv-qualification外,例如const )。

Therefore, one way you can force the move constructor to be called is to combine the object initialization with implicit type conversion: 因此,可以强制调用move构造函数的一种方法是将对象初始化与隐式类型转换结合在一起

#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;
}

You are calling X's char* constructor X("test") explicitly. 您正在显式调用X's char*构造函数X("test")

Therefore it is printing ctor 因此正在打印ctor

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM