微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

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

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


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

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

如何使我的返回值可用于移动操作。返回值是否应该是作为 ap_n& 的引用?返回对象应该用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"; }

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

如何使我的返回值可用于移动操作。返回值是否应该是 ap_n& 的引用?返回对象是否应该被 std::move(tmp) 封装?还是就这样?

按值返回结果。 (不要返回对局部变量的引用,因为局部变量会立即超出范围,使引用无效。)您可能会发现这篇简短的文章很有用:Tip of the Week #77: Temporaries,Moves,and Copies。如需更多深度,请查看cppreference on copy elision

,

如何使临时对象可移动

通过为您的类型定义移动构造函数/赋值。

返回值是否应该是 ap_n& 的引用?

返回新对象时不适用于 operator+

operator += 另一方面返回对 lhs 的引用,因此返回 ap_n&

如何使我的返回值可用于移动操作。返回对象是否应该被 std::move(tmp) 封装?还是就这样?

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

所以 return tmp; 就足够了。

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

return tmp += m; 进行复制,因为您不会“直接”返回 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,并采取行动。

C++ 如何判断一个对象何时是右值,

粗略地说,

  • 变量是左值,因为有名字。
  • 返回左值引用的函数 (ap_n&) 返回左值。
  • 函数返回 r 值引用 (ap_n&&),或按值 (ap_n) 返回 r 值。

或者决定一个移动操作适合一个对象,我如何告诉程序一个对象可以安全地用于移动操作。

重载解析选择有效候选之间的最佳匹配。

所以它需要按值或按右值引用(或转发引用)取函数。

我的第二个问题是

似乎不是第二个;-)

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

是否有必要像这样写出所有可能的组合?

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

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

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);
}

如果我删除除第一个函数以外的所有函数,程序还能正确执行移动操作吗?

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...

因此,如果出现额外的过载,您可能会用复制来移动。

,

Jarod42's answer做笔记,以下是修改后的代码。

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 个,因为同时使用两个右值引用的函数将自动使用第二个函数。

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。