如果使用多个is-a继承,您是否不想使用相互基类的虚拟继承?

时间:2011-03-22 08:43:03

标签: c++ oop inheritance

如果你有一个使用公共继承实现的继承关系,并且有一个继承的钻石你会有类似的东西:

  • 一个流类
  • 从流
  • 派生的输入流和输出流类
  • 从两者派生的输入/输出流类

在这种情况下,正如标准库(?)中所使用的那样,iostream既是-a istream又是-o ostream,istream是a-a流,而ostream是-stream流,而且是相同的流,流中的任何函数,应用于iostream,应该处理相同的底层结构。

在C ++中,为了能够共享istream和ostream中的流副本,它必须由它们虚拟地继承。

但是,如果您愿意,可以虚拟地继承,每次引用基类的成员时,请指定两个副本中的哪一个(一个在istream中,一个在ostream中)想要(通过强制转换,或使用范围:: blah)。

我的问题是,[编辑:还有其他任何情况]除了“这不是真正的关系,我使用了naughtily使用的公共继承作为语法上的方便,当它在概念上没有效用”或“我永远不需要从最派生的类中多态地引用基类,因此难以置信的小开销是不值得的”,有任何理由它在概念上有效继承非虚拟的并且有两个副本的基础每个姐妹中级班一个?

7 个答案:

答案 0 :(得分:3)

让我们来做一个简单的例子。

   B           B1   B2
   |             \ /
   D              D

在左侧,我们找到了一个来自单个低音的类。在OO设计中明显合理的是有一个关于Liskov Substitution Principal的关系。

在右侧,B1B2都是D的独立基础,D可以使用B1*或{{1}进行多态访问(或引用)。希望我们可以接受这也是一个有效的面向对象设计(尽管Sun认为它对Java程序员来说太过于紧张; -P)。然后考虑将B2*B1中的一些基本功能考虑在内并使其可重复使用:假设它们都在可以合理授予直接/ B2访问权限的任意数字列表上运行:

public

如果还没有点击,可能是:

   Container<int>   Container<int>
              \     /
              B1   B2
                \ /
                 D

可以合理地说 Container<int> Container<int> \ / Ages Heights \ / Population 是一个Ages,但它可能会添加一些方便的功能,例如Container<int>averagemin,{ {1}}。 max也是如此,可能有一组不同的便利功能。显然,num_teenagers可以合理地替代HeightsPopulation集合(例如Ages)。

这里的要点是每个支持容器并不意味着与Heights等进一步派生的类具有1:1的关系;相反,它的意思是与其直接派生的类完全相同。

同样,使用组合或继承是一种界面设计决策,但以这种方式公开容器并不一定无效。 (如果担心维护size_t num_adults(Ages&); if (num_adults(my_population)) ...不变量(例如Population},Population的数据变异函数可能会Ages::empty() == Heights::empty() Container<int>成员函数{{1} }}。)

如您所见,protectedconst没有明确的is-a关系,代码可能需要明确的歧义消除。那是合适的。当然,如果public存储了一些其他数字,PopulationContainer<int>派生也是可能合理的,但这将独立于间接继承的容器。

  

我的问题是,除了“这不是一个真正的关系之外,我使用了naughtily使用的公共继承作为语法上的便利,当它在概念上没有效用时”

我认为上述的is-a关系或概念有效性没有问题,如果你愿意解释....

  

“我永远不需要从最派生的类中多态地引用基类,因此非常小的开销是不值得的”

我认为很清楚我只是在做基于自然对象关系的数据建模,而不是一些可疑的优化。从基础中考虑的支持容器类实际上是使用的,并且必须在每个中独立。

答案 1 :(得分:2)

我想说它更有可能造成破坏。

让我们忽略有状态基类的情况。很明显,两个不相干的状态至少可能引起混淆,并且容易出错。

我宁愿专注于身份问题。在C ++中,对象的标识由其地址决定。这就是为什么除了空基类之外,每个对象必须至少具有一个字节的大小。

如果您的层次结构中有多次相同的基类,那么您可以获得两个引用同一对象的Base* ...但它们不同(指向差异地址)。

当然,你可以拉扯你的技巧并使用dynamic_cast<void*>(p)获得整个对象的“真实”物理地址......但仍然。

答案 2 :(得分:1)

问题是身份问题;在iostream的情况下 层次结构,共享基础甚至有状态,由设置 操纵。如果有多个实例 basic_ios,你有两个状态副本(格式化 标志,错误状态,甚至streambuf),这将是 一场灾难。

我真的不能想到你想要两份副本的情况 基类,但我想它们确实存在。

答案 3 :(得分:0)

一个例子

class Category
{   
public:
    virtual std::string CatName(); 
};

class OxygenBreath : public Category
{
public:
    virtual void Breath() = 0;
    std::string CatName(){ return "OxygenBreath";}
};

class LandWalk : public Category  
{
public:
    virtual void Walk() = 0;
    std::string CatName(){ return "LandWalk";}
};

class Human : public OxygenBreath, public LandWalk
{};

答案 4 :(得分:0)

可能存在这种情况有用的情况。说一个源自touchscreendisplay_device的{​​{1}}课程?现在假设每个base 公共基类有一个input_device字段,避免虚拟继承会不会有用吗?

答案 5 :(得分:0)

从理论上讲,我可以想象有一个理由继承“姐妹”B和C来自普通的非虚拟私人,然后从A和B继承公开一些“复合类”D。

当然,它不是大多数派生(D)和大多数基类(A)之间的is_a关系,因为私有继承没有表现出与用户和后代之间的is_a关系。

但是,如果B和C姐妹类不是专门针对这种情况设计的(即钻石继承),那么它们可能是公开继承自A。

效果几乎与私人继承的效果相同:我们无法从D访问A成员( [已编辑:至少没有明确地向姐妹投射; ] 任何尝试都是模棱两可的)。 因此,可以将公共非虚拟继承视为私有非虚拟继承的替代。

但我永远不会做这样的伎俩。 Iheritance本身是一个有用的比喻,但不是宇宙的基础,使用它的微妙特征会导致不必要的复杂设计。

IMO,如果用虚拟的替代简单(非虚拟)继承(概念上)是不可接受的,那么几乎肯定存在设计缺陷。

所以我会回答“不”(尽管我的“从根本上说......”结构:)

答案 6 :(得分:0)

struct managed
{
    report(std::string what);

private:
    manager_type manager;

protected:
    managed(manager_type);
};

struct human : managed
{
    human() : managed(god) {}
};

struct robot : managed
{
    robot(manager_type owner) : managed(owner) {}
};

struct employee : managed
{
    employee(manager_type boss) : managed(boss) {}
};

struct human_employee : human, employee
{
    human_employee(manager_type boss) : employee(boss) {}
};

struct robot_employee : robot, employee
{
    robot_employee(manager_type owner) : robot(owner), employee(owner) {}
};

现在,考虑一下:

void do_some_duty(const employee& e)
{
    e.do_some_tasks();
    e.report("done");
}

void face_disaster(const human& h)
{
    h.report("Oh my!");
}

void commit_suicide(const managed& m)
{
    m.report("I want to suicide");
}

作为一名人员,如果您不工作,那么您报告的人员就会有所不同:

human_employee h;
if (h.at_work()) commit_suicide(static_cast<const employee&>(h));
else commit_suicide(static_cast<const human&>(h));

如果我真的需要,我甚至会考虑使用这样的设计。您可以想象代替managed某些类,它将存储对全局管理器对象的引用,例如。垃圾收集。在这种情况下,为基础对象提供不同的基类确实很有意义。