24

假设我们有这个类:

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-ctor 应该按照标准调用吗?如果是这样,为什么不叫它?

4

3 回答 3

30

您所看到的是复制省略,它允许编译器直接将临时构造到要复制/移动到的目标中,从而省略复制(或移动)构造函数/析构函数对。允许编译器应用复制省略的情况在 C++11 标准的 §12.8.32 中指定:

当满足某些条件时,允许实现省略类对象的复制/移动构造,即使该对象的复制/移动构造函数和/或析构函数具有副作用。在这种情况下,实现将省略的复制/移动操作的源和目标简单地视为引用同一对象的两种不同方式,并且该对象的销毁发生在两个对象本应被删除的较晚时间。没有优化就被破坏了。这种复制/移动操作的省略,称为复制省略,在以下情况下是允许的(可能结合起来消除多个副本):


  • 在具有类返回类型的函数的 return 语句中,当表达式是具有与函数返回类型相同的 cv 非限定类型的非易失性自动对象的名称时,
    可以通过构造自动
    对象直接转化为函数的返回值
  • 在 throw 表达式中,当操作数是非易失性自动对象的名称,其范围不超出最内层封闭 try 块(如果有)的末尾时,复制/移动操作从操作数到可以通过将自动对象直接构造到异常对象中来省略异常对象(15.1)
  • 当尚未绑定到引用 (12.2) 的临时类对象将被复制/移动到具有相同 cv 非限定类型的类对象时,可以通过将临时对象直接构造到目标中来省略复制/移动操作省略的
    复制/移动
  • 当异常处理程序的异常声明(第 15 条)声明与异常对象(15.1)具有相同类型的对象(cv 限定除外)时,可以通过将 异常声明视为别名
    来省略复制/移动操作
    对于异常
    对象,如果程序的含义将保持不变,除非为
    异常声明声明的对象执行构造函数和析构函数。
于 2012-10-27T11:15:36.827 回答
3

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

该标准规定:

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

一个重要条件是源对象和目标是同一类型(除了 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;
}
于 2012-10-27T11:27:20.410 回答
0

您正在显式调用X's char*构造X("test")函数。

因此它正在打印ctor

于 2012-10-27T11:16:07.210 回答