当前的共识是您应该首先实现所有不创建新对象的 ?= 运算符。根据异常安全是否是一个问题(在您的情况下可能不是)或目标, ?= 运算符的定义可能会有所不同。之后你实现运营商?作为使用按值传递语义的 ?= 运算符的自由函数。
// thread safety is not a problem
class Q
{
double w,x,y,z;
public:
// constructors, other operators, other methods... omitted
Q& operator+=( Q const & rhs ) {
w += rhs.w;
x += rhs.x;
y += rhs.y;
z += rhs.z;
return *this;
}
};
Q operator+( Q lhs, Q const & rhs ) {
lhs += rhs;
return lhs;
}
这具有以下优点:
- 只有一种逻辑实现。如果类发生变化,您只需要重新实现 operator?= 和 operator? 会自动适应。
- 自由函数运算符关于隐式编译器转换是对称的
- 它是最有效的操作符实现吗?你可以找到关于副本
运营商的效率?
当你打电话给接线员?在两个元素上,必须创建并返回第三个对象。使用上面的方法,复制是在方法调用中执行的。实际上,当您传递临时对象时,编译器能够删除副本。请注意,这应该被理解为“编译器知道它可以删除副本”,而不是“编译器将删除副本”。里程会因不同的编译器而异,甚至相同的编译器在不同的编译运行中也会产生不同的结果(由于优化器可用的参数或资源不同)。
在以下代码中,将使用 和 的总和创建一个临时对象,a
并且b
该临时对象必须再次传递给operator+
withc
以创建具有最终结果的第二个临时对象:
Q a, b, c;
// initialize values
Q d = a + b + c;
如果operator+
具有按值传递语义,编译器可以省略按值传递副本(编译器知道临时对象将在第二次operator+
调用后立即被破坏,并且不需要创建不同的副本来传递)
即使operator?
可以在代码中将其实现为单行函数 ( Q operator+( Q lhs, Q const & rhs ) { return lhs+=rhs; }
),也不应如此。原因是编译器无法知道返回的引用是否operator?=
实际上是对同一对象的引用。通过使 return 语句显式获取lhs
对象,编译器知道可以省略返回副本。
关于类型的对称性
如果存在从 typeT
到 type的隐式转换Q
,并且您有两个实例t
,并且q
分别具有每种类型,那么您期望(t+q)
并且(q+t)
两者都是可调用的。如果您operator+
在内部实现为成员函数Q
,则编译器将无法将t
对象转换为临时Q
对象并稍后调用(Q(t)+q)
,因为它无法在左侧执行类型转换以调用成员函数。因此,带有成员函数的实现t+q
将无法编译。
Note that this is also true for operators that are not symmetric in arithmetic terms, we are talking about types. If you can substract a T
from a Q
by promoting the T
to a Q
, then there is no reason not to be able to substract a Q
from a T
with another automatic promotion.