编译器不生成移动构造函数

Compiler not generating move constructors

我试图了解移动语义正在查看编译器生成的移动构造函数(复制和赋值)。 在 Modern Effective C++ 中,Scott Meyers 在项目 #17 中说,如果没有显式声明复制构造函数,编译器将生成移动构造函数,它将为 non-static 成员。

为了确认这一点,我正在尝试以下代码:

#include <iostream>
#include <string>
using namespace std;

class A
{
private:
    std::string str;

public:

    A() : str("Init string")
    {
        cout << "Default constructor" << endl;
    }

    A(std::string _str) : str(_str)
    {
        cout << "Constructor with string" << endl;
    }

    std::string getString()
    {
        return str;
    }
};

int main() {

    A obj1;
    A obj2("Obj2 string");

    cout << endl;
    cout << "obj1: " << obj1.getString() << endl;
    cout << "obj2: " << obj2.getString() << endl;

    obj1 = std::move(obj2);

    cout << endl;
    cout << "obj1: " << obj1.getString() << endl;
    cout << "obj2: " << obj2.getString() << endl;

    return 0;
}

输出为:

Default constructor
Constructor with string

obj1: Init string
obj2: Obj2 string

obj1: Obj2 string
obj2: Obj2 string

但我预计会是:

Default constructor
Constructor with string

obj1: Init string
obj2: Obj2 string

obj1: Obj2 string
obj2: 

因为 obj2.str 会被移动,现在有一个空字符串。

编译器不生成移动赋值构造函数和调用复制赋值运算符的原因是什么?

编辑: 如下实现移动赋值运算符会给出预期的输出(即调用 std::move 后的空字符串)

A& operator=(A&& obj)
    {
        cout << "Move assignment operator" << endl;
        str = std::move(obj.str);
        return *this;
    }

标准未指定从对象移出的状态。

17.6.5.15 Moved-from state of library types [lib.types.movedfrom]

Objects of types defined in the C++ standard library may be moved from (12.8). Move operations may be explicitly specified or implicitly generated. Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.

因此,您从字符串中移出的内容处于有效但未指定的状态。不要排除空字符串或相同的值或包含 "potato".

的字符串

我的猜测是 "Obj2 string" 适合小字符串优化,它使小字符串能够存在于堆栈而不是堆上。在这种特殊情况下,通过 memcpy 将字符串对象分配给另一个对象而不进行任何清理(例如,不将旧字符串设置为空)实际上更快。

编译器正在调用移动赋值运算符,这导致 obj1.strobj2.str 移动赋值。但是,移动并不能保证源对象为空;对于大多数标准库 类,已移动的对象留在 "valid but unspecified state" 中。 (最明显的例外是被移出的 std::unique_ptr<T> 保证为空。)移出的 std::string 经常是空的,但并非总是如此。在您的情况下,字符串 "Obj2 string" 足够短,可以内联存储(,使用短字符串优化)。如果是这种情况,则移动赋值运算符必须 copy 字符串。返回并清空源字符串会 增加额外的开销 ,因此实现不会这样做。

首先,obj1 = std::move(obj2);调用赋值运算符,与构造函数无关

是的,编译器为A生成一个移动赋值运算符,执行成员移动操作,包括数据成员str。问题是移动操作后 str 留在 valid, but unspecified state. Also see std::basic_string::operator=.

Replaces the contents with those of str using move semantics. str is in a valid but unspecified state afterwards.

我认为您可能会观察到仅 std::string 的相同结果,例如

std::string str1 = "Init string";
std::string str2 = "Obj2 string";
str1 = std::move(str2);
std::cout << str2;

LIVE with clang,仅供参考;它给出了您预期的结果,但仍然记得结果未指定。