从具体类派生一个抽象类

时间:2008-11-21 22:20:35

标签: c++ inheritance abstract-class pure-virtual

假设我们有一个具体的class Apple。 (Apple对象可以实例化。) 现在,有人来到Apple并从Apple派生出一个抽象的class Peach。它是抽象的,因为它引入了一个新的纯虚函数。 Peach的用户现在被迫从中派生出来并定义这个新功能。这是一种常见的模式吗?这是正确的吗?

样品:


class Apple
{
public:
    virtual void MakePie();
    // more stuff here
};

class Peach : public Apple { public: virtual void MakeDeliciousDesserts() = 0; // more stuff here };

现在让我们说我们有一个具体的class Berry。有人从Berry派生出一个抽象的class Tomato。它是抽象的,因为它覆盖了Berry的虚拟函数之一,并使其成为纯虚拟函数。 Tomato的用户必须重新实现之前在Berry中定义的功能。这是一种常见的模式吗?这是正确的吗?

样品:


class Berry
{
public:
    virtual void EatYummyPie();
    // more stuff here
};

class Tomato : public Berry { public: virtual void EatYummyPie() = 0; // more stuff here };

注意:名称是人为的,并不反映任何实际代码(希望如此)。在撰写这个问题时,没有任何成果受到伤害。

7 个答案:

答案 0 :(得分:8)

来自Apple的Re Peach:

  • 如果Apple是价值类,请不要这样做 (即复制ctor,不相同 实例可以相等,等等。见迈耶斯 更有效的C ++项目33为什么。
  • 如果Apple公开,请不要这样做 非虚拟析构函数,否则你 当你的时候邀请未定义的行为 用户通过a删除Apple 指向Peach的指针。
  • 否则,您可能很安全,因为您没有违反Liskov substitutability。桃子IS-A Apple。
  • 如果您拥有Apple代码,则更喜欢将常见的抽象基类(也许是Fruit)分解出来,并从中派生出Apple和Peach。

来自Berry的Re Tomato:

  • 同上,加上:
  • 避免,因为这是不寻常的
  • 如果必须,请记录番茄的派生类必须做的事情,以免违反Liskov的可替代性。你在Berry中重写的功能 - 让我们称之为Juice() - 强加某些要求并作出某些承诺。派生类的Juice()实现必须不再需要,并且不会更少。然后,DerivedTomato IS-A Berry和只知道Berry的代码是安全的。

通过记录DerivedTomatoes必须致电Berry::Juice(),您可能会满足最后一项要求。如果是这样,请考虑使用模板方法:

class Tomato : public Berry
{
public:
    void Juice() 
    {
        PrepareJuice();
        Berry::Juice();
    }
    virtual void PrepareJuice() = 0;
};

现在有一个非常好的机会,番茄IS-A浆果,违反植物学的期望。 (例外情况是,派生类的PrepareJuice实现强加了超出Berry::Juice强加的前提条件。

答案 1 :(得分:5)

在我看来,这似乎表明设计糟糕。如果你想从一个封闭的库中获取一个具体的定义并扩展它并从中分出一堆东西,可能会被强制,但在那时我会认真考虑关于封装而不是继承的指导原则。如果你可以封装的话,你可能应该。

是的,我想的越多,这是一个非常糟糕的主意。

答案 2 :(得分:2)

不一定错误,但肯定是臭的。特别是如果你把水果放在阳光下太久。 (而且我认为我的牙医不会喜欢吃混凝土苹果。)

尽管如此,我在这里看到的主要问题不是抽象类派生自具体类,而是REALLY DEEP继承层次结构。

编辑:重新阅读我看到这是两个层次结构。所有的水果都让我混淆了。

答案 3 :(得分:2)

如果您使用推荐的继承模型“is-a”,那么这种模式几乎不会出现。

一旦你有了一个具体的类,你就是说你可以实际创建一个实例。如果你从那里派生出一个抽象类,那么作为基类属性的东西对于派生类来说就不是真的,派生类应该设置一些不正确的klaxons。

看看你的例子,桃子不是苹果,所以它不应该来自它。来自Berry的番茄也是如此。

这是我通常建议收容的地方,但这似乎不是一个好模型,因为Apple不包含Peach。

在这种情况下,我会考虑公共接口--PieFilling或DessertItem。

答案 4 :(得分:1)

有点不寻常,但如果您有基类的其他子类,并且抽象类的子类有足够的常用东西来证明抽象类的存在,例如:

class Concrete
{
public:
    virtual void eat() {}
};
class Sub::public Concrete { // some concrete subclass
    virtual void eat() {}
};
class Abstract:public Concrete // abstract subclass
{
public:
    virtual void eat()=0;
    // and some stuff common to Sub1 and Sub2
};
class Sub1:public Abstract {
    void eat() {}
};
class Sub2:public Abstract {
    void eat() {}
};
int main() {
    Concrete *sub1=new Sub1(),*sub2=new Sub2();
    sub1->eat();
    sub2->eat();
    return 0;
}

答案 5 :(得分:0)

嗯......通过思考“什么是.....”几秒钟,我得出结论并不常见...... 另外,我不会从Berry派生出Apple和Apple的Peach ......你有更好的例子吗?:)

你可以用C ++做很多奇怪的事情......我甚至不能想到它的1%......

关于使用纯虚拟覆盖虚拟,您可能只是隐藏它,这将非常奇怪......

如果你能找到一个将这个函数作为虚函数链接起来的愚蠢的C ++编译器,那么你将获得运行时纯虚函数调用......

我认为这只能用于黑客攻击而且我不知道究竟是什么样的黑客......

答案 6 :(得分:0)

要回答你的第一个问题,你可以这样做,因为Apple的用户,如果给出一个源自Peach的具体实例,将不会有任何不同。并且实例不会知道它不是Apple(除非有一些来自Apple的虚拟函数被覆盖而你没有告诉我们)。

我无法想象用纯虚拟功能覆盖虚拟功能会有多大用处 - 甚至合法吗?

一般来说,你想要与他的书中的Scott Meyers“Make all non-leaf classes abstract”项目保持一致。

无论如何,除了那些你所描述的似乎是合法的 - 只是我不能经常看到你需要它。