C ++ public virtual inhertiance of interface with private inheritance of implementation

时间:2017-10-27 14:28:06

标签: c++ multiple-inheritance

我的问题是为什么以下代码的行为与它一样。我不是在询问设计的质量(我知道你们中的一些人会立即讨厌多重继承,我不是在这里争论或反对)我可以问一个单独的问题,该问题涉及作者试图实现,但我们假设存在与此等效的代码: -

class IReadableData
{
    public: virtual int getX() const = 0;
};

class Data : public virtual IReadableData
{
   public: 
      virtual int getX() const { return m_x; }
      void setX(int x) {m_x = x;}
   private:
      int m_x;   
};

class ReadableData : private Data, public virtual IReadableData
{
     // I'd expected to need a using here to expose Data::getX
};

这符合视觉工作室2017的“警告C4250:'ReadableData':通过支配继承'Data :: Data :: getX'”

首先,我有点惊讶没有被告知ReadableData没有实现getX(鉴于它的实现是私有的),但没有发生警告我可以创建一个ReadableData,但是尽管ReadableData公开继承自IReadableData,公共方法IReadableData无法访问

    ReadableData r;
    //  r.getX(); error C2247: 'Data::getX' not accessible because 'ReadableData' uses 'private' to inherit from 'Data'

但是以下编译

    ReadableData r;
    IReadableData& r2 = r;
    r2.getX();

这似乎与我不一致r或者是IReadableData并且getX应该可用或者它不是,并且对r2(或ReadableData的定义)的赋值应该失败。这会发生吗?标准中有什么导致这个?

这个问题: - Diamond inheritance with mixed inheritance modifers (protected / private / public)

似乎是连通的,除了那个案例中的基础不是抽象的,它的答案引用第11.6节会让我认为r.getX();应该是可以访问的。

编辑:我使示例代码略微不那么简单,只是对意图有一点了解,但它并没有真正改变这个问题。

2 个答案:

答案 0 :(得分:3)

它是查找规则的衍生物,它具有一些与虚拟继承相关的项目符号。这个想法是虚拟继承不应该在名称查找过程中引起歧义。因此,在每个场景中都会找到getX()的不同声明。由于访问说明符仅在名称查找后检查(并且不影响名称解析),因此您可以遇到此类问题。

  • r2.getX();的情况下,查询从IReadableData的上下文开始。由于立即发现声明,并且IReadableData没有基础,因此这是查找解析的内容。它是IReadableData的公共成员,因此您可以命名并调用它。在那之后,它是动态调度机制,负责调用由支配Data::getX()给出的实现。

  • 对于r.getX();,查找的工作方式不同。它始于ReadableData的上下文。没有getX()的声明存在,所以转向查看ReadableData的直接基础。这里的事情有点不直观:

    1. 检查IReadableData,找到IReadableData::getX()。它注意到这个查找,以及它所在的IReadableData基础子对象。
    2. 检查Data,找到Data::getX()。还记下了此查找的注释以及它所在的Data基础子对象。
    3. 现在它尝试将#1和#2的查找集合并到ReadableData的查找集中。由于#1中的IReadableData子对象也是来自#2的Data子对象的子对象(由于虚拟继承),所以#1都被完全忽略。 / LI>
    4. 在步骤#3中仅留下Data::getX(),查找将解析为它。

    5. 所以r.getX();实际上是r.Data::getX();。查找发现的是什么。此时检查访问说明符。这是导致错误的原因。

我所说的一切都是试图打破[class.member.lookup]部分标准中描述的过程。我并不想引用这个标准,因为我觉得它并没有用于解释用简单英语发生的事情。但您可以按照链接阅读完整的规范。

答案 1 :(得分:1)

访问修饰符始终是静态解析,而不是动态解析,即它基于静态而非动态类型。实现IReadableData的对象的合同严格来说,指向该对象的指针/引用可以别名为IReadableData,然后可以在其上有意义地调用方法getX。毕竟,多态契约的全部意义在于多态地使用它们。关于直接使用派生对象时会发生什么,没有真正的保证或必要性。

因此,从这个意义上说,允许派生对象更改访问说明符,然后根据静态类型而不是动态类型解析访问说明符,至少是一个与多态契约概念一致的选择。 / p>

所有这一切,在任何方面改变派生对象中的访问说明符并不是一个好主意。没有上升空间,因为解决它是微不足道的,所以没有封装优势,只是暴露了这个奇怪的边缘情况。

设计明智我并不是从根本上反对多重继承。然而,99%的情况下,钻石是你最好避免的东西。私有继承也几乎没有用,并且它们更容易枚举:

  1. 用于空基类优化。
  2. 如果其他人编写了一个具有虚函数的类作为定制它的技术,那么您需要此类来实现您的类。我说"别人"因为更现代的C ++中的这种设计很难证明,现在它更容易传递函数/ lambda / std::function