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

时间:2016-11-11 14:45:31

标签: c++ oop inheritance

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

       Operations
           |
           ↓
        Binops 
         |  |
         |  |
     +---+  +---+
     ↓          ↓
  Addition  Subtraction

此处BinopsOperations的朋友类。需要以下成员函数: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的朋友就足够了,因为友谊不是传递性的。

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

1 个答案:

答案 0 :(得分:5)

看来这些类名有点偏。我的通灵解码是AdditionHasAddition。我们HasOperations继承自HasBinOps,继承自HasAdditionHasSubtraction

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

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

幸运的是,在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;
  }
};

Live example

现在,++=都是根据您的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(替换失败不是错误)来检测addsubtactmultiplydivideorder,{{ 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中看到更加充实的版本,它也使用相关技术。