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

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

提问人:emesx 提问时间:10/27/2012 更新时间:6/26/2023 访问量:9566

问:

假设我们有这个类:

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。我希望最后一个对象 () 是移动构造的,但事实并非如此!如果我使用,那么输出的最后一行是 ,正如我所期望的那样。是 (N)RVO 的情况吗?zX z( move(X("test")) );ctor move test

:是否应该按照标准调用移动器?如果是这样,为什么不叫它?

C++ C++11 移动语义

评论

2赞 Benjamin Lindley 10/27/2012
这是复制省略。如果复制省略失败,则将发生移动。为什么你的帖子标题说“默认构造函数首选”?没有调用默认构造函数,也没有首选任何内容来代替移动构造函数。它正在被完全消除。
0赞 M.M 2/21/2017
此代码应该无法编译,因为 C++11;字符串文本不能隐式转换为 non-const 。char *
0赞 underscore_d 4/17/2018
什么是复制省略和返回值优化?
0赞 Nilesh Kumar 9/17/2020
如果您使用的是 G++ then pass 标志,这将关闭复制省略,并且您的移动 CTOR 将被调用-fno-elide-constructors

答:

32赞 Grizzly 10/27/2012 #1

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

当满足某些条件时,允许实现省略 类对象的复制/移动构造,即使复制/移动 对象的构造函数和/或析构函数具有副作用。在这样的 情况下,实现处理省略的来源和目标 复制/移动操作只是引用 同一对象,并且该对象的破坏发生在较晚的 如果没有 优化。这种复制/移动的省略 以下操作中允许执行称为复制省略的操作 情况(可以组合起来消除多个副本):

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

评论

1赞 emesx 10/27/2012
你能提供一个简单的例子,不使用它会强制编译器使用移动构造函数吗?std::move
0赞 Grizzly 10/27/2012
@elmes:当没有必要调用移动构造函数时,为什么要调用移动构造函数?
2赞 Benjamin Lindley 10/27/2012
@elmes:X z((rand() % 2) ? X("test") : X("TEST"));
1赞 Xeo 10/27/2012
@Grizzly:C++11 要求当复制省略成为可能时(您从函数返回相同类型的对象),每个人都将首先尝试移动 .不需要明确的移动,这会妨碍 (N)RVO。return local_var;local_var
2赞 Grizzly 10/27/2012
@Xeo:我不跟着。即使在所描述的情况下,该标准也可能允许复制省略,但是编译器是否能够做到这一点,特别是如果生命周期重叠,是值得怀疑的。我在哪里说过在返回局部变量时需要(或可执行)显式移动?
0赞 Anirudha 10/27/2012 #2

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

因此,它是印刷ctor

评论

1赞 Xeo 10/27/2012
然后在声明时调用 的 move 构造函数。Xz
3赞 jogojapan 10/27/2012 #3

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

该标准规定:

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

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

评论

1赞 Bojan Komazec 7/2/2017
可以看作是移动构造函数吗?N3936 草案 (12.8.3) 说:“类 X 的非模板构造函数是移动构造函数,如果它的第一个参数是 X&、常量 X&&、易失性 X&& 或常量易失性 X&&,并且没有其他参数,或者所有其他参数都有默认参数”A::A(B&& b)
0赞 Israel Unterman 6/26/2023 #4

只是想评论一下,如果你只想确保移动 ctor 工作,你可以破解代码,通过抛出一个条件来消除编译器优化,例如:

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