C++11 move 语义

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

我一直在尝试通过 Bjarne Stroustrup 精彩的 C++ 书籍自学在 C++11 中正确使用 move 语义。我遇到了一个问题—— 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(1) + getTest(2) + getTest(3)) 的结果是一个右值(因为它没有名字,因此,不可能是在将其分配给变量 test 后使用),因此默认情况下应使用 move 构造函数构造它,而不需要显式调用 std::move()。

有人可以解释为什么会出现这种行为吗?我做错了什么吗?我只是误解了 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

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

我认为现在可以正确理解左值/右值/move 语义魔法。

最佳答案

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+ 的实现中使用它的奖励积分。

关于C++11 move 语义,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21641918/

相关文章:

c++11 - 在 Red Hat linux 中在 g++ 4.4.7 上编译 C++11

c++ - 成员初始值设定项中使用的 sizeof 的行为是什么?

c++ - 是否有 move const 对象的有用场景?

c++ - 因为我们有 move 语义,所以不需要为 STL 容器参数使用 const & 吗?

c++ - 将 Unicode 文本放置在具有 NULL 所有者窗口的剪贴板上时出现意外行为

c++ - 如何自定义监 window 口格式

c++ - 确保程序在终端中

c++ - 使用专门的模板并命名枚举选项

c++ - 用于 move std::shared_ptr 的函数声明

C++:在 ofstream 文件中写一个双近似值,比如 printf("%f....)