【问题标题】:C++11 Move SemanticsC++11 移动语义
【发布时间】:2014-02-08 13:36:20
【问题描述】:

我一直在尝试通过 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

但是,将 main 函数更改为以下内容:

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)) 的结果是一个右值(因为它没有名字,因此,没有办法在将其分配给变量 test 后使用),因此默认情况下应使用 move 构造函数构造它,而不需要显式调用 std::move()。

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

谢谢。

编辑 1:

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

在类定义中添加:

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

哪个更具描述性。通过实现右值 operator+,不会为每次使用 operator+ 创建一个新对象,这意味着 operator+ 的长链将具有明显更好的性能。

我认为现在可以正确理解这种左值/右值/移动语义魔术了。

【问题讨论】:

  • 您的operator+ 非常不寻常。
  • operator + 应该返回一个值实例,而不是一个引用。你已经实现了operator +,就好像它是operator +=

标签: c++ c++11 move-semantics


【解决方案1】:

getTest(1) + getTest(2) + getTest(3) 的结果与Test::operator+(const Test&amp;) 的返回类型具有相同的类型。它是Test&amp;,因此是一个左值。

operator + 通常是按值返回临时值的非成员重载:

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

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

以成员身份实现operator +=,并在非成员operator+ 的实现中使用它的奖励积分。

【讨论】:

  • 我用一些修改后的代码更新了原始问题。我想我明白现在发生了什么。感谢您的帮助。
猜你喜欢
  • 1970-01-01
  • 2014-08-16
  • 2019-01-02
  • 2014-03-18
  • 2023-03-26
  • 1970-01-01
  • 2011-06-26
相关资源
最近更新 更多