在现代 C++ 中重载算术运算符的 "best" 方法是什么?
What is the "best" way to overload arithmetic operators in modern C++?
我想在 C++ 中实现一种 "number-like" 数学对象(例如,一组或环中的元素)。我敢肯定,我不是唯一处理此问题的人,因此可能会有大量关于重载算术运算符的 "best" 方式的讨论。但是,我找不到满意的答案(虽然可能是我懒得去谷歌搜索了)。
假设我想为 class A 重载运算符“+”。
问题是,我能想到的重载太多了:
- 运算符+(const A& x, const A& y)
- 运算符+(const A& x, A&& y)
- 运算符+(A&& x, const A& y)
- 运算符+(A&& x, A&& y)
- A::operator+=(const A& y) &
- A::operator+=(const A& y) &&
- A::operator+=(A&& y) &
- A::operator+=(A&& y) &&
第一个问题。是否需要所有这些重载才能使对 A 实例的操作尽可能高效并尽可能多地达到 "being like primitive types"?我认为对于 "ordinary" 情况,右值限定的 A::operator+= 不需要(或不应该)重载。这样对吗?我认为所有1-4都是必要的。例如,对于情况 3,由于 x 正在移动,我们不需要分配新的 space 来保存 return 值,我们可以安全地重用为 x 保留的分配存储。对吗?
第二个问题。我认为对于大多数此类情况,所有这些重载可能共享很多代码。如何在不牺牲 performance/etc. 的情况下最大限度地减少代码重复?有什么特殊的techniques/idioms可以做到这一点吗?
如果存在的话,我更喜欢通用和可扩展的方法。
不可能给出最笼统的答案,因为选择 "best" 方式将涉及对操作细节的理解。
例如,最常见的模式(我在下面给出)不太适合矩阵乘法,因为在这种情况下,最简单的方法是声明第三个矩阵,该矩阵从零开始并读取两个参数。此外,您可能希望使用延迟评估,在这种情况下 none 适用。
我建议确保您的代码针对所有情况给出正确的答案,一旦您的程序运行并且您对语言有了更多的经验,您就可以稍后担心微优化。
对于 class,其中实现 +
的最有效方法是修改其中一个参数,则以下两种情况涵盖所有用途,并提供强大的异常保证:
A& A::operator+=(A const &y) { /* modify *this using y */ ; return *this; }
A operator+ ( A x, A const& y ) { x += y; return x; }
有关以上代码的作用和原因的更多解释,请参阅 operator overloading megathread。
在 C++03 中,使用 A const& x
而不是 A x
并没有多大区别,但在 C++11 中,这对于第一个参数的情况稍微更优化是一个右值,因为现在可以从第一个参数中窃取资源。
关于在 operator+=
上使用 ref-qualifiers 的决定。为 &
和 &&
单独重载没有任何好处。如果您看到有人使用 &
,其基本原理不是也重载,而是在尝试对右值使用 +=
时给出编译错误;理由是这很可能是一个错误。
我想在 C++ 中实现一种 "number-like" 数学对象(例如,一组或环中的元素)。我敢肯定,我不是唯一处理此问题的人,因此可能会有大量关于重载算术运算符的 "best" 方式的讨论。但是,我找不到满意的答案(虽然可能是我懒得去谷歌搜索了)。
假设我想为 class A 重载运算符“+”。 问题是,我能想到的重载太多了:
- 运算符+(const A& x, const A& y)
- 运算符+(const A& x, A&& y)
- 运算符+(A&& x, const A& y)
- 运算符+(A&& x, A&& y)
- A::operator+=(const A& y) &
- A::operator+=(const A& y) &&
- A::operator+=(A&& y) &
- A::operator+=(A&& y) &&
第一个问题。是否需要所有这些重载才能使对 A 实例的操作尽可能高效并尽可能多地达到 "being like primitive types"?我认为对于 "ordinary" 情况,右值限定的 A::operator+= 不需要(或不应该)重载。这样对吗?我认为所有1-4都是必要的。例如,对于情况 3,由于 x 正在移动,我们不需要分配新的 space 来保存 return 值,我们可以安全地重用为 x 保留的分配存储。对吗?
第二个问题。我认为对于大多数此类情况,所有这些重载可能共享很多代码。如何在不牺牲 performance/etc. 的情况下最大限度地减少代码重复?有什么特殊的techniques/idioms可以做到这一点吗? 如果存在的话,我更喜欢通用和可扩展的方法。
不可能给出最笼统的答案,因为选择 "best" 方式将涉及对操作细节的理解。
例如,最常见的模式(我在下面给出)不太适合矩阵乘法,因为在这种情况下,最简单的方法是声明第三个矩阵,该矩阵从零开始并读取两个参数。此外,您可能希望使用延迟评估,在这种情况下 none 适用。
我建议确保您的代码针对所有情况给出正确的答案,一旦您的程序运行并且您对语言有了更多的经验,您就可以稍后担心微优化。
对于 class,其中实现 +
的最有效方法是修改其中一个参数,则以下两种情况涵盖所有用途,并提供强大的异常保证:
A& A::operator+=(A const &y) { /* modify *this using y */ ; return *this; }
A operator+ ( A x, A const& y ) { x += y; return x; }
有关以上代码的作用和原因的更多解释,请参阅 operator overloading megathread。
在 C++03 中,使用 A const& x
而不是 A x
并没有多大区别,但在 C++11 中,这对于第一个参数的情况稍微更优化是一个右值,因为现在可以从第一个参数中窃取资源。
关于在 operator+=
上使用 ref-qualifiers 的决定。为 &
和 &&
单独重载没有任何好处。如果您看到有人使用 &
,其基本原理不是也重载,而是在尝试对右值使用 +=
时给出编译错误;理由是这很可能是一个错误。