2012-10-27 62 views
14

假设我们有此类:C++ 11移动的构造不叫,默认的构造优选

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吗?如果是这样,为什么不叫?

+2

它复制省略。如果复制失败,则会发生移动。为什么你的文章标题说“首选默认构造函数”?没有默认的构造函数被调用,并且没有任何东西被替代移动构造函数。它正在被完全消除。 –

+0

自C++ 11以来,此代码将无法编译;字符串文字不能隐式转换为非const char *'。 –

回答

19

你看到的是copy elision,它允许编译器直接构造一个临时对象到它要被复制/移动到的对象中,从而避免构造器/析构器对的复制(或移动)。其中,编译器允许应用复制省略的情况下在C++ 11标准的§12.8.32指定:

当满足特定条件时,一种实现被允许省略 复制/移动构造一个类对象,即使该对象的构造函数和/或析构函数具有副作用。在这种情况下,实施将对复制/移动操作省略的源和目标作为简单的两种不同的方式来引用同一对象,并且销毁该对象发生在两个时间的较晚的时间,其中两个如果没有进行优化,对象就会被销毁。一份,省音/移动 操作,称为复制省略,在下面的 情况下才允许(这可能合并,以消除多个副本):

    return语句中
  • 与类返回类型,当一个函数表达式是一个非易失性自动对象的名称,它与函数返回类型具有相同的cv-unquali fi ed类型,可以通过构造自动的
    对象直接进入函数的返回值来省略复制/移动操作
  • in throw-ex当操作数是非范围的自动对象的名称,其作用域的范围不超出 内部最后的try-block的末尾(如果有的话), 从操作数复制/移动到异常对象(15.1) 可以通过将自动对象直接构造成 异常对象
  • 当没有绑定到引用的临时类对象2)将被复制/移动到一个类对象与他 相同CV-unquali音响版型,复制/移动操作可以通过 可以省略直接构建临时对象到
    省略副本的目标/移动
  • 时异常处理程序(第15章)的异常声明声明异常对象(15.1)为
    相同类型的对象(除cv资格外),可以省略复制/移动操作
    通过处理异常声明作为例外
    对象的别名,如果程序的含义将是除了 执行构造的和不变由
    声明异常声明的对象的析构函数。
+1

你可以提供一个简单的例子,不使用'std :: move'来强迫编译器使用移动构造函数吗? – emesx

+0

@elmes:你为什么要在不需要的时候调用移动构造函数? – Grizzly

+0

我想看看任何使用'X'类的例子,只是为了相信定义一个有意义。 – emesx

0

要调用X'schar*构造X("test")明确。

因此打印ctor

+1

然后在声明'z'时调用'X'的移动构造函数。 – Xeo

3

ctor输出你在第三行代码得到的是对临时对象的构造。在那之后,的确,临时移到新变量z。在这种情况下,编译器可能会选择复制/移动,而这看起来就是它所做的。

标准状态:

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

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

能否将'A :: A(B && b)'视为_move构造函数_? N3936草案(12.8.3)说:“如果类X的非模板构造函数的第一个参数是X &&,const X &&,volatile X &&或const volatile X &&,并且没有其他参数或否则所有其他参数都有默认参数“ –