我一直在尝试通过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))的结果是一个右值(因为它没有名称,因此在其后无法使用)被分配给变量test),因此默认情况下应使用move构造函数构造它,而不是要求显式调用std::move()。
有人可以解释为什么发生这种现象吗?我做错什么了吗?我是否只是误解了移动语义学的基础?
谢谢。
编辑1:
我更新了代码以反射(reflect)以下一些评论。
在类定义中添加:
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 +时,都必须创建一个临时对象。
有趣的是,如果我在 Release模式下编译修订后的代码,则永远不会调用move构造函数,而在 Debug模式下,将如上面的控制台输出所描述的那样调用它。
编辑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
更具描述性。通过实现右值运算符+,不会为每次使用运算符+创建一个新对象,这意味着较长的运算符+链将具有更好的性能。
我认为现在应该正确理解这种左值/右值/移动语义魔术。
最佳答案
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+
的实现中使用它的加分点。