在C ++中使用继承时避免不需要的函数声明/定义

时间:2012-10-26 19:46:35

标签: c++ inheritance virtual

经过长时间的C风格程序编码,我才开始“获得”OOP。所以我怀疑可能有标准的方法来处理我所面临的情况。我有一个应用程序,其类层次结构如下所示:

#include <iostream>
using namespace std;

class A {
public:
  virtual int intf() { return 0;} // Only needed by B
  virtual double df() {return 0.0;} // Only needed by C
};    
class B : public A {
  int intf() {return 2;}
  // B objects have no use for df()
};    
class C : public B {
  double df() {return 3.14;}
  // C objects have no use for intf()
};    
int main(){
  // Main needs to instantiate both B and C.
  B b;
  C c;
  A* pa2b = &b;
  A* pa2c = &c;

  cout << pa2b->intf() << endl;
  cout << pa2b->df() << endl;
  cout << pa2c->intf() << endl;
  cout << pa2c->df() << endl;

  return 0;
}

现在这个程序编译并运行正常。但是,我对它的设计有疑问。 A类是通用接口,不需要实例化。 B类和C类需要。关于函数:B需要intf()而不是C,而C需要df()而不是B.如果我在A中使intf(){df()}纯虚,那么就没有合理的定义对于B {C},df(){intf()}。

编辑 B和C共享一些数据成员以及f()以外的一些成员函数。我没有向我展示我的精简代码。

最后,作为标准,我的应用程序需要通过指向A的指针访问B和C.所以我的问题是:有没有办法'清理'这个设计,以便不需要/空成员函数定义(如正如我在A)的声明/定义中所做的那样可以消除吗?这些类之间存在明确的“IS-A”关系。所以即使我分享每个新手对继承的兴奋,我也不觉得我已经延伸了我的设计,所以我可以使用继承。

后台以防万一:我正在实施一个回归套件。 A类实现每个回归共有的函数和矩阵(例如依赖变量和独立变量)。 B类是逻辑回归,有两个类('0'和'1'),定义了成本函数,以及两类逻辑回归的训练算法。 C类是多类逻辑回归。它通过使用“one-vs-all”算法训练多个类来扩展B类。因此,在某种意义上,如果您认为您感兴趣的类作为正例,而所有其他作为反面例子,则C是二元逻辑回归。然后,您为每个类执行此操作以实现多类回归。有问题的函数(intf和df)返回输出。在逻辑回归的情况下,返回值是向量,而对于多类回归,它是矩阵。并且,如上所述,B和C对彼此的返回功能没有任何用处。除了我似乎无法消除A(回归类)中的冗余定义。

感谢您的帮助。

2 个答案:

答案 0 :(得分:5)

查看利斯科夫替代原则(http://en.wikipedia.org/wiki/Liskov_substitution_principle)。它声明子类必须履行与超类相同的合同。在您的示例中,两个子类都不会这样做。 “Is-A”关系不足以证明继承的合理性。

一种选择是使用单个模板方法,如下所示:

template <typename T>
class A<T> {
    T getValue();
}

class B : A<int> {
    int getValue();
}

class C: A<double> {
    double getValue();
}

这将允许两个子类履行合同,同时允许方法的返回类型根据子类定义而变化。

如果你想学习更多面向对象的编程“最佳实践”,谷歌“罗伯特马丁SOLID”

答案 1 :(得分:3)

你触及了OOP最具争议的一点:is-a ==派生模式,导致了“上帝对象”的反模式,因为一切都是神的孩子,上帝知道每一种方法每个人都有一个“答案”(读作“默认实施”)。

“Is-a”不足以证明继承是合理的,在这种情况下不存在替换能力,但在现实世界中,任何对象都不能完全替换为另一个对象,否则它就不会有所不同。

你处于“无处可去”的地方,替换原则不能很好地运作,但是 - 同时 - 虚函数看起来是实现动态调度的最佳工具。

你唯一可以做的就是达成妥协,并牺牲其中一个。

到目前为止,由于B和C没有任何共同之处(没有共享的有用方法),所以根本不要让这些方法来自A. 如果您有“共享”的内容,则可能是在输入B相关特定代码或C相关特定代码之前发现B或C类型的运行时机制。

这通常使用一个具有运行时类型指示符的公共库来完成,或者只是一个虚拟函数(通常是析构函数),以使dynamic_cast能够工作。

class A
{
public:
    virtual ~A() {}

    template<class T>
    T* is() { return dynamic_cast<T*>(this); }
};

class B: public A
{
public:
    int intf() { return 2; }
};

class C: public A
{
public:
    double df() { return 3.14; }
};

int main()
{
    using namespace std;

    B b;
    C c;

    A* ba = &b;
    A* ca = &c;

    B* pb = ba->is<B>();
    if(pb) cout << pb->intf() << endl;

    C* pc = ca->is<C>();
    if(pc) cout << pc->df() << endl;
}