【发布时间】: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