C++11移动语义

3

我一直在尝试通过Bjarne Stroustrup精彩的C++书籍来自学C++11中移动语义的正确使用。但是我遇到了一个问题——移动构造函数并没有按照我的预期被调用。请看下面的代码:

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(1)+getTest(2)+getTest(3))的结果是一个rvalue(因为它没有名称,因此,在变量test被赋值后无法使用),所以默认情况下应该使用移动构造函数进行构造,而不需要显式调用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

我相信这种情况下应该发生的就是大量的新对象创建,但仔细思考后,这是有道理的,因为每次调用operator+时都必须创建一个临时对象。

有趣的是,如果我在发布模式下编译修改后的代码,则不会调用移动构造函数,但在调试模式下,正如上面的控制台输出所描述的那样,它会被调用。

编辑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语义魔法。


7
您的 operator+ 非常不寻常。 - Kerrek SB
3
operator + 应该返回一个值实例,而不是引用。你把 operator + 实现成了 operator += 的样子。 - WhozCraig
1个回答

3

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

operator + 通常是一个返回临时值的非成员重载函数:

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

或者

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

如果您将 operator += 作为成员实现,并在非成员的 operator+ 的实现中使用它,则会获得额外的加分。


我已更新原问题,附上了一些修改过的代码。现在我认为我明白发生了什么。感谢帮助。 - user3286280

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接