在 C++ 上覆盖运算符时促进 MOVE 操作的正确方法

Proper way to facilitate MOVE operation when overriding operator on C++

我不太熟悉移动在 C++ 中的工作原理,我需要一些帮助来澄清我的理解。我想重载 operator+,对此我有几个问题。


ap_n operator+(const ap_n& n, const ap_n& m) {
    ap_n tmp {n};
    return tmp += m;
}

我的第一个问题是如何使临时对象可移动。如我上面的函数所示,两个参数都不会被改变,因此我需要创建第三个对象来执行操作。

如何使我的 return 值可用于移动操作。 return 值应该作为 ap_n& 的参考吗? return对象应该被std::move(tmp)封装吗?还是就这样好了?

C++ 如何确定对象何时为右值,或确定移动操作适合对象,以及我如何告诉程序对象可安全地用于移动操作。


ap_n operator+(const ap_n&, const ap_n&); // defined as in first question
ap_n operator+(ap_n&& n, const ap_n& m) { return n += m; }
ap_n operator+(const ap_n& n, ap_n&& m) { return m += n; }
ap_n operator+(ap_n&& n, ap_n&& m) { return n += m; }

我的第二个问题是是否有必要创建接受右值参数的函数变体。现在我有 4 个函数,如图所示,能够接受普通对象和右值对象。

有必要把所有可能的组合都写成这样吗?如果我删除除第一个功能之外的所有功能,程序是否仍然能够正确执行移动操作?

作为调试提示,可以帮助正确处理这些事情的是在移动构造函数中打印一条消息

ap_n(ap_n&& o): x_(std::move(o.x_)) { std::cerr << "Move constructed\n"; }

加上其他构造函数和析构函数中的类似消息。然后,您将清楚地了解实例创建和销毁的时间和方式。

How can I make my return value usable for move operation. Is the return value supposed to be a reference as ap_n&? Should the return object be encapsulated by std::move(tmp)? Or is it alright as is?

Return 值的结果。 (不要 return 对局部变量的引用,因为局部变量会立即超出范围,使引用无效。)您可能会发现这篇简短的文章很有用:Tip of the Week #77: Temporaries, Moves, and Copies. For more depth, check out cppreference on copy elision.

how to make temporary objects movable

通过定义 move constructor/assignment 到你的类型。

Is the return value supposed to be a reference as ap_n&?

不适用于 operator+ 当您 return 新对象时。

operator += 另一方面 returns 引用 lhs,所以 returns ap_n&.

How can I make my return value usable for move operation. Should the return object be encapsulated by std::move(tmp)? Or is it alright as is?

来自return statement, 当直接 returning 局部变量时会自动移动。

所以return tmp;就足够了。

return std::move(tmp); 阻止 NRVO

return tmp += m; 进行复制,因为您不会 return “直接” tmp.

你应该做的:

ap_n operator+(const ap_n& n, const ap_n& m) {
    ap_n tmp {n};
    tmp += m;
    return tmp; // NRVO, or automatic move
}

return std::move(tmp += m); 将阻止 NRVO,并执行移动。

How does C++ decide when an object is rvalue,

大致上,

  • 变量是左值,因为有名称。
  • 函数 returning 左值引用 (ap_n&) returns 左值。
  • 函数 returning 右值引用 (ap_n&&),或按值 (ap_n) returns 右值。

or decide that a move operation is suitable on an object, and how can I tell the program that an object is safe to use for move operation.

过载分辨率select有效候选之间的最佳匹配。

因此需要函数取值或右值引用(或转发引用)。

My second question is

好像不是第二个 ;-)

whether it is necessary to create variants of function that accept rvalue arguments. Right now I have 4 functions, as shown, to be able to accept normal objects and rvalue objects.

Is writing all the combinations possible like this necessary?

在一般情况下,通过 const 引用或值获取单个函数就足够了, 除非你想要那个优化。所以主要用于库编写器或关键代码。

请注意,您的重载应该重写以有效地执行移动操作(以重用输入的临时参数):

ap_n operator+(ap_n&& n, const ap_n& m)
{
    n += m;
    return std::move(n); // C++11; C++14, C++17
    return n; // C++20
}

ap_n&& operator+(ap_n&& n, const ap_n& m)
{
    return std::move(n += m);
}

If I remove all but the first function, would the program still be able to perform move operation correctly?

ap_n operator+(const ap_n& n, const ap_n& m) {
    ap_n tmp {n}; // copy
    tmp += m;
    return tmp; // NRVO, or automatic move
}

你有 1 个副本,一个 NRVO/move 用于任何类型的参数。

ap_n&& operator+(ap_n&& n, const ap_n& m) {
    return std::move(n += m);
}

你没有移动,但你应该注意引用的生命周期,因为

auto ok = ap_n() + ap_n(); // 1 extra move
auto&& dangling = ap_n() + ap_n(); // No move, but no lifetime extension...

ap_n operator+(ap_n&& n, const ap_n& m) {
    n += m;
    return std::move(n); // C++11, C++14, C++17 // move
    return n; // C++20 // automatic move
}

你有 1 步,没有副本。

auto ok = ap_n() + ap_n(); // 1 extra move possibly elided pre-C++17, 0 extra moves since C++17
auto&& ok2 = ap_n() + ap_n(); // No moves, and lifetime extension...

因此,如果有额外的超载,您可能会用副本来移动。

参考,修改后的代码如下

ap_n operator+(const ap_n& n, const ap_n& m) {
    ap_n tmp {n};
    tmp += n;
    return tmp; // Allows copy, NRVO, or move
}
ap_n operator+(ap_n&& n, const ap_n& m) {
    n += m;
    return std::move(n); // Allows copy, or move
}
ap_n operator+(const ap_n& n, ap_n&& m) {
    m += n;
    return std::move(m); // Allows copy, or move
}

函数的数量也减少到 3 个,因为同时采用两个右值引用的函数将自动使用第二个函数。

如果我仍然误解这一点,请告诉我。