【问题标题】:Am I in a specific case justifying multiple inheritance?我是否在特定情况下证明多重继承是合理的?
【发布时间】:2015-04-28 13:31:03
【问题描述】:

目前,我有以下类管理不同类型的变量:

class Variable;
class Number : public Variable;
class Boolean : public Variable;
class RealNumber : public Number;
class IntegerNumber : public Number;

这是一个经典的继承树,它运行良好。我管理Number* 实例的向量和Boolean* 实例的向量。

我想添加另一种类型的变量:具有特定行为但界面相似的“虚拟变量”。然后,我想使用“虚拟数字”向量和“虚拟布尔值”向量。

第一个解决方案是在Variable 中添加一个标志,但我更喜欢编译安全性并管理std::vector<VirtualResponse*>std::vector<VirtualBoolean*>,以保证变量在向量中是虚拟的。

然后,我想也许我在一个特定的情况下多重继承是合理的,但我是多重继承的新手。 您对这类课程有何看法?

class VirtualVariable : public virtual Variable;
class VirtualNumber : public virtual Number, public virtual VirtualVariable;
class VirtualBoolean : public virtual Boolean, public virtual VirtualVariable;
class VirtualRealNumber : public virtual RealNumber, public virtual VirtualNumber;
class VirtualIntegerNumber : public virtual IntegerNumber, public virtual VirtualVariable;

这是一种经典的方式吗?会不会是陷阱?我是否滥用了virtual 关键字?

编辑:我想做的例子:

void my_function(const std::vector<SpecItem>& spec)
{
  // First : create the description from the spec
  std::vector<Number*> descr;
  std::vector<VirtualNumber*> virt_descr;
  for(unsigned long int i = 0; i < spec.size(); ++i)
  {
    if(spec[i].isNumber())
      if(spec[i].isReal())
      {
        double lower_bound = spec[i].lowerBound();
        double upper_bound = spec[i].upperBound();
        if(spec[i].isVirtual())
        {
          std::string formula = spec[i].formula();
          virt_descr.push_back(new VirtualRealNumber(lower_bound,upper_bound,formula));
        }
        else
          descr.push_back(new RealNumber(lower_bound,upper_bound));
      }
      else if(spec[i].isInteger())
      {
        long int lower_bound = ceil(spec[i].lowerBound());
        long int upper_bound = floor(spec[i].upperBound());
        if(spec[i].isVirtual())
        {
          std::string formula = spec[i].formula();
          virt_descr.push_back(new VirtualIntegerNumber(lower_bound,upper_bound,formula));
        }
        else
          descr.push_back(new IntegerNumber(lower_bound,upper_bound));
      }
  }
  // Now, descr is a vector of Number*, virt_descr is a vector of VirtualNumber*

  // Second step : assign values to the numbers
  std::vector<double> values;
  for(unsigned long int i = 0; i < descr.size(); ++i)
  {
    double new_value = (descr[i]->lowerBound() + descr[i]->upperBound()) / 2.0;
    values.push_back(descr[i]->adjust(new_value));
  }

  // Third step : evaluate virtual numbers
  std::vector<double> virt_values;
  for(unsigned long int i = 0; i < virt_descr.size(); ++i)
  {
    double new_value = virt_descr[i]->evaluate(values);
    values.push_back(descr[i]->adjust(new_value));
  }

  return 0;
}

【问题讨论】:

  • 我建议你看看这个multiple-inheritance example(在你能提供一个例子来演示上述指导方针吗?),看看是否有任何替代方案可以很好地工作给你。
  • 我的观点是,如果不首先了解目标,就无法正确设计继承(多个或其他,虚拟或其他)。编写继承或面向对象的设计不能成为目标。那么这里的目标是什么?你想达到什么目的?
  • 管理 std::vector&lt;VirtualNumber*&gt;std::vector&lt;VirtualBoolean*&gt; 对象的方式与我目前使用 std::vector&lt;Number*&gt; 的一些算法相同。 VirtualVariables 在评估时只有特定的行为,但从数学的角度来看保持相同的行为。
  • 这不是信息。您使用的语言中,子类型关系和接口不仅限于继承,还有多种继承等。为了理解您的问题,需要解释类型的用例。你开始做的第一件事是,让 C++ 继承包装一个整数,这是值得怀疑的:即使是对 oo 上瘾的语言也会害怕。

标签: c++ inheritance multiple-inheritance


【解决方案1】:

多重继承旨在通过确保separation of concerns 来支持简洁的设计。所以可以说是完全有道理的。

在不了解原因和限制的情况下,很难就可能的最佳班级设计提出建议。但是有一些问题可能会对您有所帮助。

从以下行开始:

    class VirtualVariable : public virtual Variable;

1) 你需要虚拟继承吗?

在您了解虚拟继承之前,多重继承确实简单而有趣。这意味着对于从VirtualVariable 多次继承的类,所有VirtualVariable 子对象实例将共享一个Variable 基对象。如需更多信息,请阅读diamond problem

在您的设计中,对于Variable 的每个继承(包括非“虚拟”的),您可能都需要它,以避免有多个Variable 子对象,而您实际上应该只有一个。您不应该将它用于其他继承。

这种虚拟继承将要求,对于每个构造函数,您显式地初始化您的虚拟基。

2) VariableVirtualVariable 之间的真正关系是什么?

根据您的解释,我了解到“虚拟性”概念独立于“变量”概念。另一方面,在您的代码中,您假装VirtualVariable 是一个 Variable

如果它是独立的,你应该保持它独立。这将是更好的关注点分离。好消息是您不再需要虚拟继承:

class Virtuality;     
class VirtualVariable : public Variable, public Virtuality;
class VirtualNumber : public Number, public Virtuality;
class VirtualBoolean : public Boolean, public Virtuality;
class VirtualRealNumber : public RealNumber, public Virtuality;
class VirtualIntegerNumber : public IntegerNumber, public Virtuality;

3) 你看过模板吗?

你说:

第一个解决方案是在变量中添加一个标志,但我更喜欢 编译安全和管理

如果只是为了编译类型检查,你可以使用模板:

template <bool isvirtual>
Variable { ...};
template <bool isvirtual>
class Number :  Variable<isvirtual> { ...};
...
std::vector<Number<true>*> v; 

对于比变量更复杂的事物,您可以使用基于策略的设计。粗略地说,不是提供一个 bool 作为模板参数,而是提供一个实用程序类作为参数,该类封装了一系列行为。

编辑:供您编​​辑

解决方案 2 和 3 应该完全能够实现您打算执行的操作。

我在此处发布了具有多态性的解决方案 2 的 online demo,以展示您可以轻松地混合“虚拟”和非“虚拟”元素。它定义了一个函数IntegerNumber add (IntegerNumber &amp;a, IntegerNumber&amp;b);,并在这个场景中使用它:

IntegerNumber n(27);  // number with an inital value
VirtualIntegerNumber k;   // value will be calculated from some formula 
add (n, k);            // ok, my demo just displays the value of each parameter ;-)

*顺便说一句,我认为您代码中的构造 if (isxxx())... if (isyyy()) ... 强烈建议您应该使用多态性。

【讨论】:

  • 感谢您的回答。我希望能够在现实中操纵IntegerNumber*RealNumber*Number* 指针,但我也希望在现实中操纵VirtualIntegerNumber*VirtualRealNumber*VirtualNumber* 指针。认为您的解决方案 2) 和 3) 是不可能的,对吧?
  • 我认为应该。您能否编辑您的问题,展示您认为适用于您的方法但不适用于 2 和 3 的示例,以便我可以在需要时完善我的答案?
  • @Caduchon 我已经编辑了答案,包括一个源代码链接,展示了一个简短的概念证明。
  • 感谢您的回答。我必须再读一遍,但实际上它似乎有效。 ;-) 关于if (isxxx())... if (isyyy())的构造,这是有道理的,因为它来自JSON格式,并为示例进行了简化。
【解决方案2】:

我尽量避免多重继承。 听起来您想要一个对象,该对象的行为类似于基础对象,但会针对特定情况进行更改。听起来像一个类规范。我不确定您的目标是什么,但也许您可以将您的设计更改为经典的层次模型。为了实现这一点,将所有特定代码放在单独的函数中,然后在基类中调用。

class Number : public Variable
{
    ...
    protected:
        virtual void functionWithSpecialUsage() { // Empty in base class, used by inheritance only };
}

class VirtualNumber : public Number
{
    ...
    protected:
        void functionWithSpecialUsage() { // Add class specific behavior here };
}

【讨论】:

  • 这个层次结构有两个问题。 1/ 我必须重写每个子类中的虚拟功能。 2/ 如果我定义class VirtualRealNumber : public RealNumber,我无法管理VirtualNumber*的向量。
  • 要遍历一组类,您需要为它们选择基类。在你的情况下是一个 std::vector。您可以使用动态或静态投射来投射它们。
  • 没有。我需要从级别 2 指向我的类。Variable 只是避免代码重复的常见类型。事实上,我在我的 2 级子类中实现了像 cloneAsNumber() const 这样的函数。对变量的每次访问都进行动态转换对我来说真的太慢了​​。
  • 我没有得到这个目标。也许您共享更多代码或用例。如果您想将自己的数字用于具有高性能的东西(例如物理模拟),那么它是一种更好的方式来接受代码重复以提高性能并创建没有继承的每个类。
  • Variable 是一个非常大的抽象类。我有 9 个类继承自它。真的没有理由为了避免工作模式而重复 9 次数千行代码...
猜你喜欢
  • 2021-01-06
  • 1970-01-01
  • 2016-07-05
  • 2020-10-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多