在 C++ 中实现复杂的继承

Posted

技术标签:

【中文标题】在 C++ 中实现复杂的继承【英文标题】:Implementing complex inheritance in C++ 【发布时间】:2009-06-23 10:40:59 【问题描述】:

我有以下现有课程:

class Gaussian 
public:
  virtual Vector get_mean() = 0;
  virtual Matrix get_covariance() = 0;
  virtual double calculate_likelihood(Vector &data) = 0;
;

class Diagonal_Gaussian : public Gaussian 
public:
  virtual Vector get_mean();
  virtual Matrix get_covariance();
  virtual double calculate_likelihood(Vector &data);
private:
  Vector m_mean;
  Vector m_covariance;
;

class FullCov_Gaussian : public Gaussian 
public:
  virtual Vector get_mean();
  virtual Matrix get_covariance();
  virtual double calculate_likelihood(Vector &data);
private:
  Vector m_mean;
  Matrix m_covariance;
;

如您所见,Gaussian 类充当接口,但没有任何实现。这一切都很好。

现在我想创建一个“AdaptedGaussian”类,其中提供给计算似然的数据向量将在计算似然度之前发生变化。

一些要求:

AdaptedGaussian 必须是 Gaussian 的子类 AdaptedGaussian 必须能够“包装”或“成为”每个可能的高斯类的实例 AdaptedGaussian 必须从已经存在的高斯对象构造

我现在的想法是:

class Adapted_Gaussian : public Gaussian 
private:
  Gaussian* m_g;

public:
  virtual Vector get_mean()  return m_g->get_mean(); 
  virtual Matrix get_covariance()  return m_g->get_covariance(); 
  virtual double calculate_likelihood(Vector &data) 
   
    //do something with data
    return g->calculate_likelihood(Vector &data); 
  
 

可能有一些缺点:

对于每个方法(这里显示的不止一个),必须在新类中编写一个虚拟方法 如果 Gaussian 被扩展,而这个类会被遗忘,就会出现令人讨厌的错误。

我这样做的方式是否正确?还是有更好的方法来实现这一点?

是否有一种好方法可以标准地将每个未实现的方法委托给 m_g 的同一个命名方法?

【问题讨论】:

我在这里不明白的是:你真的在 calculate_likelihood 中调用“get_covariance”还是这只是一个错误,你想调用 calculate_likelihood?我必须承认,我的统计数据非常生疏...... 哎,那是个错误。谢谢,我更正了。 顺便说一句。 calculate_likelihood 真的无效吗?别这么想!! 你又是对的。对不起,我的马虎。 【参考方案1】:

看起来不错,我认为这是适配器模式的一个非常经典的实现。只是不要忘记为您的高斯类声明一个虚拟析构函数。至于缺点。

    Java 类库处理伪方法问题的方式是创建一个伪类,为每个方法提供空实现。所有不想实现每个方法的类都可以从这个虚拟类继承,并有选择地覆盖他们感兴趣的方法。 如果您使用更多方法扩展 Gaussian 类,只要将它们声明为纯虚拟方法,您的子类文件中就会出现编译器错误。

【讨论】:

【参考方案2】:

正如您所指出的,编写大量基本的传递函数很乏味,并且会增加隐含的维护开销。此外,拥有指针成员意味着拥有指针的额外(尽管简单)生命周期管理问题。解决这些问题的最简单方法可能是使 AdaptedGaussian 成为模板,以要适应的 Gaussian 特定实例为模板。

template<class BaseGaussian> class AdaptedGaussian : public BaseGaussian

    virtual double calculate_likelihood(Vector &data) 
     
        // do something with data
        return BaseGaussian::calculate_likelihood(Vector &data); 
    
;

这确实依赖于所有适用的 Gaussian 实例都是默认可构造的,或者至少符合通用构造函数签名。

如果你想从现有的XXXGaussian 构造一个AdaptedGaussian,那么只要XXXGaussian 本身是可复制的,你就可以添加一个合适的构造函数:

template<class BaseGaussian> class AdaptedGaussian : public BaseGaussian

public:
    AdaptedGaussian(const BaseGaussian& other) : BaseGaussian(other)
    
    
    // ...
;

【讨论】:

嗯,当我只有一个指向现有高斯的指针时,我将如何构造一个 AdaptedGaussian? 也许我理解错了,在这个解决方案中,AdaptedGaussian 是适应的特定高斯类型的一个实例。如果你想要一个适应的对角高斯,你可以创建一个 AdaptedGaussian 而不是一个普通的 Diagonal_Gaussian。由于 AdaptedGaussian 是 T,您可以使用指向 AdaptedGaussian 的指针和引用作为指向 T 的指针和引用。 当您说:'AdaptedGaussian 必须能够“包装”或“成为”每个可能的高斯类的实例'时,我认为 (a) 包装现有实例不是必需的,并且,因此(b)您对指针的使用是一个实现细节,因为您的目标是“包装”解决方案而不是“实例”解决方案。我是否错过了您问题的一些关键要求? 很抱歉,我的问题并不完全清楚。要求是该类以某种方式代表一个已经存在的类。我不知道或者是否存在一种方法可以实现这一点而无需将其称为包装? 在这种情况下,我认为使用我添加的构造函数,这个解决方案可以工作。不过,这取决于您的具体情况。正确的 AdaptedGaussian 的构造取决于具有正确静态类型的指针或引用。如果你有一个 Diagonal_Gaussian 指针,那么你可以从中构造一个 AdaptedGaussian ,如果你只有一个恰好指向 Diagonal_Gaussian 实例的高斯指针,那么你已经丢弃了一些静态类型信息,并且需要一个取决于关于运行时多态性,例如您发布的内容。【参考方案3】:

这也可以通过Strategy Pattern 解决。

在我看来,duffymo 也在用“构图”来思考这个方向。以这种方式更改设计,即基类调用它包含的其他对象的某些方法。该对象包含calculate_likelihood 的编码。可以推迟整个方法,也可以只推迟修改(在第二种情况下,默认是什么都不做)。

例如:(修正版)

class Gaussian 
   private:
      Cl_Strategy* m_cl_strategy;

   public:
      Gaussian(Cl_Strategy* cl_strategy) 
         m_cl_strategy = cl_strategy;
      ;
      virtual Vector get_mean() = 0;
      virtual Matrix get_covariance() = 0;
      virtual double _calc_likelihood(Vector &data) = 0;
      virtual double calculate_likelihood(Vector &data) 
         m_cl_strategy->do_your_worst(this, data);
         return _calc_likelihood(data);
      ;
;

我希望,我做对了,我的 C++ 有点尘埃落定...

_calc_likelihood 必须由子类实现,并且 calculate_likelihood 绑定在一起。

当然,这种解决方案会增加一点开销,但在某些情况下,开销可能还可以。

【讨论】:

这个想法并不完全奏效。每个子类都有自己的 calculate_likelihood 实现,因此没有适应的数据。除此之外,我希望完全在当前课程之外拥有此功能。这是因为此功能可能以不同的方式/方法实现,并且仅在少数应用程序运行中使用。 感谢您的反馈。当然,我也可以尽可能多地暗示,因为我理解这个问题。这就是我写“也许”的原因。从您的代码 sn-ps 中,整个上下文并不那么清楚。当每个子类都有自己的calculate_likelihood实现时,你的例子怎么能工作——因为它也对它做了一些限制?? 我不完全明白为什么我的示例不起作用。因为calculate_likelihood在Gaussian中是虚的(并没有实现,见= 0),所以会执行真实对象的calculate_likelihood函数。 Gaussian* 指针始终指向 Gaussian 子类的实例。 查看我对问题的评论。【参考方案4】:

在 Java 中,通常同​​时拥有一个接口和一个实现它的抽象类,以便为所有方法提供默认行为。 (请参阅 Joshua Bloch 在 java.util 包中对 Collections API 的设计。)也许这对您也有帮助。您将为客户提供使用接口或抽象类的选择。

你也可以试试composition。将适应的高斯实例传递给子类并将行为推迟到它。

【讨论】:

以上是关于在 C++ 中实现复杂的继承的主要内容,如果未能解决你的问题,请参考以下文章

游戏开发在Lua中实现面向对象特性——模拟类继承多态

C++中的动态继承

如何首先在代码中实现多级继承

C++ 中的类接口继承

javascript 中实现继承的六种方式

如何从 C++ 基类继承抽象行为