对于C ++类,我试图设计一个处理二进制操作+=
和-=
的类层次结构。所需的层次结构(每个问题要求)描述如下。我们有两个班级Addition
和Subtraction
。这些是类Binops
的基类。然后,课程Operations
继承自Binops
。所以图表看起来像这样
Operations
|
↓
Binops
| |
| |
+---+ +---+
↓ ↓
Addition Subtraction
此处Binops
是Operations
的朋友类。需要以下成员函数:Operations
实现私有函数
void add(Operations const &rhs);
void sub(Operations const &rhs);
班级Addition
需要实施
Addition::operator+=(Operations const &rhs)
同样适用于Subtraction
类。
我对这个设计的实现以及它背后的想法都有疑问。
我看到它的方式,一旦这个框架准备就绪,另一个类,例如Matrix
类可以从Operations
类继承,然后我们让Matrix
成为{的朋友{1}}所以Operations
可以使用Matrix
等。然后,我们只需在+=
中实施add
函数,然后Operations
操作就可以了对于+=
类。但是,为什么我们不在Matrix
甚至+=
中实施Operations
运算符?也许我们的想法是,我们还可以使用Matrix
函数=
在Addition
中定义add
运算符,以便在实现Operations
之后,add
和} +=
一起工作。
从实施的角度来看:+
中+=
的返回类型应该是什么?我认为它应该是Addition
,但是Operations
类标题应该包含Addition
标题,这会导致循环依赖。
此外,要让Operations
能够使用Addition
中的add
,我们可以通过某种方式做到这一点,而不会让Operations
成为Addition
的朋友也呢?我不认为让Operations
成为Addition
的朋友就足够了,因为友谊不是传递性的。
很抱歉这个问题很长。提前感谢任何见解!
答案 0 :(得分:5)
看来这些类名有点偏。我的通灵解码是Addition
是HasAddition
。我们HasOperations
继承自HasBinOps
,继承自HasAddition
和HasSubtraction
。
所以我得到了基本的计划。但我要回答如何做到这一点。这可能与你的作业不符,但老实说你的作业不是我的作业!
我们不希望所有基本操作都进行虚拟运行时调度和动态分配。我们想要静态多态,而不是动态多态。
幸运的是,在C ++中我们有静态多态。实现它的一种典型方法是通过CRTP - 奇怪的重复模板模式。
我们不必在这里使用CRTP。我们可以依靠Koenig查找!
Koenig查找的事实是,在确定要调用的operator+
时,会考虑父类friend
。我们在派生类型上注入friend operator+
,在template
内加has_addition
。
当我们有matrix:has_addition
时,我们会调用+
。找到此模板。然后我们替换参数的类型 - 完整类型,而不是has_addition
父类型。
在这个完整类型中,我们有一个.add
方法。
所以我们可以继承一个类型,使得该类型中的operator+
具有基于我们从中派生的类型的不同实现,但是这个调度在编译时是静态完成的。
在运行时,has_addition
基本上消失了。相反,我们只是将一堆+
路由到.add
。
所以,不用多说,这里是has_addition
:
struct has_addition {
// implement + in terms of += on the lhs:
template<class L, class R>
friend std::decay_t<L> operator+( L&& lhs, R&& rhs ) {
if (!std::is_reference<L>{}) { // rvalue lhs
return std::forward<L>(lhs) += rhs;
} else if (!std::is_reference<R>{}) { // rvalue rhs
return std::forward<R>(rhs) += lhs; // assumes + commutes
} else { // rvalue neither
auto tmp = std::forward<L>(lhs);
return tmp += rhs;
}
}
// notice += on an rvalue returns a copy.
// This permits reference lifetime extension:
template<class L, class R>
friend L operator+=( L&& lhs, R&& rhs ) {
lhs.add( std::forward<R>(rhs) );
return std::forward<L>(lhs);
}
};
您通过以下方式使用它:
struct bob : has_addition {
int x = 0;
void add( bob const& rhs ) {
x += rhs.x;
}
};
现在,+
和+=
都是根据您的add
方法为您实现的。更重要的是,它们有多个rvalue和lvalue重载。如果实现move-construct,则会获得自动性能提升。如果你实现了在右侧采用右值的add,你将获得自动性能提升。
如果你没有写rvalue重载add
和move-construct,那么事情仍然有效。我们将这些因素(添加您可以丢弃的内容,回收存储空间以及+
的工作方式的微优化)相互分离。结果更容易编写带有大量微优化内置的代码。
现在,has_addition::operator+
中的大多数微优化都不是第一次传递所必需的。
struct has_addition {
// implement + in terms of += on the lhs:
template<class L, class R>
friend L operator+( L lhs, R&& rhs ) {
return std::move(lhs) += std::forward<R>(rhs);
}
template<class L, class R>
friend L operator+=( L&& lhs, R&& rhs ) {
lhs.add( std::forward<R>(rhs) );
return std::forward<L>(lhs);
}
};
更干净,更接近最佳。
然后我们用
扩展它struct has_subtraction; // implement
struct has_binops:
has_subtraction,
has_addition
{};
struct has_operations:
has_binops
{};
但实际上,很少有类型有各种类型的操作,所以我个人不喜欢这个。
您可以使用SFINAE(替换失败不是错误)来检测add
,subtact
,multiply
,divide
,order
,{{ 1}}等在您的类型中实现,并编写equals
来对maybe_has_addition<D>
执行SFINAE测试,以确定它是否已实现D
。当且仅当D.add( D const& )
继承自has_addition
。
然后你可以设置它,以便通过执行以下操作写入无数的操作符重载:
maybe_has_addition<D>
当您在struct matrix: maybe_has_operations<matrix>
上实施新操作时,会有越来越多的重载运算符启动。
然而,这是一个不同的问题。
使用动态多态(虚函数)执行此操作是一团糟。实际上,在编写matrix
时,是否要跳过多个vtable,动态分配以及丢失所有编译时类型的安全性?这不是Java。
朋友的位很容易。请注意matrix1 = matrix2 + matrix3
如何调用has_addition
。我们可以在D.add(D const&)
内将add
设为私有,但仅限于D
正文中的friend struct has_addition;
。
因此D
既是has_addition
的父级,也是D
的朋友。
我自己,我只是让D
暴露,因为它是无害的。
这种技术有缺点,就像你添加两个不同的类add
时会发生什么。
您可以在boost.operators中看到更加充实的版本,它也使用相关技术。