C 11移动语义

C++11 Move Semantics

本文关键字:语义 移动      更新时间:2023-10-16

我一直在试图通过Bjarne Stroustrup的精彩C 书籍在C 11中正确使用Move语义。我遇到了一个问题 - 移动构造函数并没有像我期望的那样被称为。采用以下代码:

class Test
{
public:
    Test() = delete;
    Test(const Test& other) = delete;
    Test(const int value) : x(value) { std::cout << "x: " << x << " normal constructor" << std::endl; }
    Test(Test&& other) { x = other.x; other.x = 0; std::cout << "x: " << x << " move constructor" << std::endl; }
    Test& operator+(const Test& other) { x += other.x; return *this; }
    Test& operator=(const Test& other) = delete;
    Test& operator=(Test&& other) { x = other.x; other.x = 0; std::cout << "x :" << x << " move assignment" << std::endl; return *this; }
    int x;
};
Test getTest(const int value)
{
    return Test{ value };
}
int main()
{
    Test test = getTest(1) + getTest(2) + getTest(3);
}

此代码不会编译 - 因为我已删除默认复制构造函数。添加默认复制构造函数,控制台输出如下:

x: 3 normal constructor
x: 2 normal constructor
x: 1 normal constructor
x: 6 copy constructor

但是,将主函数更改为以下:

int main()
{
    Test test = std::move(getTest(1) + getTest(2) + getTest(3));
}

产生所需的控制台输出:

x: 3 normal constructor
x: 2 normal constructor
x: 1 normal constructor
x: 6 move constructor

这使我感到困惑,因为据我了解,(getTest(getTest(1) getTest(2) getTest(3))的结果是一个rvalue(因为它没有名称,因此,没有任何方法在将其分配给变量测试之后使用),因此应使用移动构造函数构建它,而不是需要向STD :: MOVE()。

进行明确调用

有人可以解释为什么这种行为发生吗?我做错了吗?我只是误解了移动语义的基础知识吗?

谢谢。

编辑1:

我更新了代码以反映下面的一些评论。

类定义中添加:

friend Test operator+(const Test& a, const Test& b) { Test temp = Test{ a.x }; temp += b; std::cout << a.x << " + " << b.x << std::endl; return temp; }
Test& operator+=(const Test& other) { x += other.x; return *this; }

更改为:

int main()
{
    Test test = getTest(1) + getTest(2) + getTest(4) + getTest(8);
}

这会产生控制台输出:

x: 8 normal constructor
x: 4 normal constructor
x: 2 normal constructor
x: 1 normal constructor
x: 1 normal constructor
1 + 2
x: 3 move constructor
x: 3 normal constructor
3 + 4
x: 7 move constructor
x: 7 normal constructor
7 + 8
x: 15 move constructor

我相信在这种情况下应该发生的事情 - 这里有很多新的对象创建,但是在更紧密的想法中,这是有道理的,因为每个时都称为操作员 ,必须创建一个临时对象。<<<<<<

有趣的是,如果我在发布模式下编译了修订的代码,则移动构造函数从未被调用,但是在调试模式下,它被称为上述控制台输出。

编辑2:

进一步完善。添加到类定义:

friend Test&& operator+(Test&& a, Test&& b) { b.x += a.x; a.x = 0; return std::move(b); }

产生控制台输出:

x: 8 normal constructor
x: 4 normal constructor
x: 2 normal constructor
x: 1 normal constructor
x: 15 move constructor

正是所需的输出。

编辑3:

我相信最好做以下内容。在类定义中编辑:

friend Test&& operator+(Test&& a, Test&& b) { b += a; return std::move(b); }
Test& operator+=(const Test& other) { std::cout << x << " += " << other.x << std::endl; x += other.x; return *this; }

这会产生控制台输出:

x: 8 normal constructor
x: 4 normal constructor
x: 2 normal constructor
x: 1 normal constructor
2 += 1
4 += 3
8 += 7
x: 15 move constructor

更具描述性。通过实现RVALUE操作员 ,不是为操作员的每一个使用创建一个新对象,这意味着操作员的长链将具有更好的性能。

我认为现在正确理解这个lvalue/rvalue/move语义魔术。

getTest(1) + getTest(2) + getTest(3)的结果与Test::operator+(const Test&)的返回类型相同。它是Test&,因此是LVALUE。

operator +通常是一个非成员过载,它返回临时值:

Test operator + (const Test& a, const Test& b)

Test operator + (Test a, const Test& b)

用于实现operator +=作为成员的奖励积分,并将其用于实现非会员operator+