多重继承.实现`+ =`的继承模型背后的想法是什么?

Slu*_*ger 3 c++ oop inheritance

对于C++类,我想设计一个类层次结构,处理二进制运算+=-=.所需的层次结构(每个问题要求)描述如下.我们有两个类AdditionSubtraction.这些是类的基类Binops.然后一个类Operations继承自Binops.所以图表看起来像这样

       Operations
           |
           ?
        Binops 
         |  |
         |  |
     +---+  +---+
     ?          ?
  Addition  Subtraction
Run Code Online (Sandbox Code Playgroud)

Binops是朋友班Operations.需要以下成员函数:Operations实现私有函数

void add(Operations const &rhs);
void sub(Operations const &rhs);
Run Code Online (Sandbox Code Playgroud)

而且这个课Addition需要实施

Addition::operator+=(Operations const &rhs)
Run Code Online (Sandbox Code Playgroud)

同样的Subtraction课程.

我对这个设计的实现以及它背后的想法都有疑问.

我看到它的样子,一旦这一框架已经准备好,例如像其他类Matrix类可以从继承Operations类,然后我们做Matrix的一个朋友Operations这样Matrix可以使用+=等等.然后,我们也只会实现add在功能Operations+=操作将然后为Matrix全班工作.但后来我们为什么不干脆直接在+=运营商Operations,甚至Matrix?也许这个想法是,我们还可以定义=在操作Addition使用add功能Operations,使实施后add,无论是+=+工作一气呵成.

从实现的角度来看:应该是什么的返回类型+=Addition?我相信它应该是Operations,但是Addition类标题应该包含Operations导致循环依赖的标头.

此外,Addition能够使用addOperations,是有一些方法,我们可以做,没有做Addition的朋友Operations吗?我不认为只是交Addition朋友Binops就足够了,因为友谊不是传递性的.

很抱歉这个问题很长.提前感谢任何见解!

Yak*_*ont 5

似乎那些类名有点偏.我的心灵解码是,AdditionHasAddition.所以我们HasOperations继承自HasBinOps,继承自HasAdditionHasSubtraction.

所以我得到了基本的计划.但我要回答如何做到这一点.这可能与你的任务不符,但老实说你的任务不是我的!

我们不希望所有基本操作都进行虚拟运行时调度和动态分配.我们想要静态多态,而不是动态多态.

幸运的是,在C++中我们有静态多态.实现它的一种典型方法是通过CRTP - 奇怪的重复模板模式.

我们不必在这里使用CRTP.我们可以依靠Koenig查找!

Koenig查找的事实是,在确定operator+要调用的内容时,friend会考虑父类.我们通过使派生类型成为内部来注入friend operator+匹配的派生类型.templatehas_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);
  }
};
Run Code Online (Sandbox Code Playgroud)

你通过以下方式使用它

struct bob : has_addition {
  int x = 0;
  void add( bob const& rhs ) {
    x += rhs.x;
  }
};
Run Code Online (Sandbox Code Playgroud)

实例.

现在,这两个++=会为您根据您的实现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);
  }
};
Run Code Online (Sandbox Code Playgroud)

这是更清洁和近乎最佳.


然后,我们将其扩展

struct has_subtraction; // implement
struct has_binops:
  has_subtraction,
  has_addition
{};

struct has_operations:
  has_binops
{};
Run Code Online (Sandbox Code Playgroud)

但实际上,很少有类型有各种类型的操作,所以我个人不喜欢这样.

你可以使用SFINAE(替换故障是不是一个错误),以检测是否add,subtact,multiply,divide,order,equals等你的类型来实现,写maybe_has_addition<D>,做一个SFINAE测试D,以确定其是否已D.add( D const& )执行.当且仅当如此has_addition继承自maybe_has_addition<D>.

然后你可以设置它,以便通过执行以下操作来编写无数的运算符重载:

struct matrix: maybe_has_operations<matrix>
Run Code Online (Sandbox Code Playgroud)

在您实施新操作的地方matrix,越来越多的重载运营商开始工作.

然而,这是一个不同的问题.


使用动态多态(虚函数)执行此操作是一团糟.实际上,您是否想要在编写时跳过多个vtable,动态分配并丢失所有编译时类型的安全性matrix1 = matrix2 + matrix3?这不是Java.


这位朋友很容易.请注意如何has_addition调用D.add(D const&).我们可以add在内部私有化D,但只有我们friend struct has_addition;在体内D.

所以has_addition既是父母,D也是朋友D.

我自己,我只是add暴露,因为它是无害的.


这种技术有缺点,就像你添加两个不同的类时所发生的那样has_addition.

您可以在boost.operators中看到更加充实的版本,它也使用相关技术.