【问题标题】:C++ public virtual inhertiance of interface with private inheritance of implementationC++接口的公共虚拟继承与实现的私有继承
【发布时间】:2017-10-27 14:28:06
【问题描述】:

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

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
};

这符合 Visual Studio 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();应该是可以访问的。

编辑:我让示例代码稍微少了一点,以便对意图有一点了解,但这并没有真正改变问题。

【问题讨论】:

  • 我很困惑...您想说“ReadableData 具有接口 IReadableData,但实现 Data”?这只是数据,你 typedef 它。
  • @UKMonkey 这是一个最小的例子,它不是真正的代码。
  • @Ron,“避免多重继承”?接口呢?甚至 C# 也允许这样做(尽管它不允许私有继承)。
  • 通常的做法是拥有一个私有的Data成员,然后ReadableData的impl封装了Data函数。继承的问题不在于多重部分,而在于它们在不应该这样做的时候这样做。
  • @UKMonkey,谢谢,是的,我同意,这就是我自己通常会这样做的方式。我仍然想了解为什么这样的代码会表现得如此。

标签: c++ multiple-inheritance


【解决方案1】:

它是查找规则的派生词,其中包含一些与虚拟继承相关的要点。这个想法是虚拟继承不应该在名称查找期间引起歧义。因此,在每个场景中都可以找到不同的 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 都被完全忽略。
    4. 在步骤#3 中只剩下Data::getX(),查找解决它。


    所以r.getX(); 实际上是r.Data::getX();。这就是查找所发现的。正是在这一点上,访问说明符被检​​查。这是你的错误的原因。

我所说的一切都是为了打破[class.member.lookup] 部分标准中描述的过程。我不想在这方面引用标准,因为我觉得用简单的英语解释发生的事情并没有多大帮助。但是您可以点击链接阅读完整的规范。

【讨论】:

  • 谢谢,我怀疑这与查找规则有关,快速查看标准表明您可能不直接引用它是正确的 - 但这是我真正追求的我提到了标准,一个基于它的答案。
【解决方案2】:

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

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

话虽如此,在派生对象中更改访问说明符无论如何都不是一个好主意。没有任何好处,因为它很容易解决,所以封装的好处为零,它只是暴露了这种奇怪的边缘情况。

在设计方面,我从根本上并不反对多重继承。但是,99% 的情况下,您最好避免使用钻石。私有继承也是如此,几乎没有用处,而且它们更易于枚举:

  1. 针对空基类优化。
  2. 如果其他人编写了一个具有虚函数的类作为自定义它的技术,并且您需要这个类来实现您的。我说“其他人”是因为更现代的 C++ 中的这种设计很难证明是合理的,现在只传递函数/lambda/std::function 更容易。

【讨论】:

  • 但是为什么r.getX(); 不起作用呢?它可能不会破坏多态性,但我不明白为什么它不应该在 ReadableData 中公开。
  • @DanielH 我不确定我是否理解这个问题。从机制上讲,就像我已经说过的:访问修饰符是静态解析的。如果您考虑一下,纯粹从性能的角度来看,在 runtime 检查访问说明符会很疯狂。几乎没有任何好处。除了检查动态类型之外,该语言在这里唯一的其他选择是禁止您更改派生类中的访问说明符。为什么 C++ 不这样做?通常 C++ 选择灵活性,而较重的 OOP 过去很流行,也许它可能会有所帮助。如今,没有那么多。
  • 是的,它们是静态解析的,这就解释了为什么ReadableData::getXIReadableData::getX 可以 有不同的访问修饰符。但是为什么,在这个特定的例子中, do 他们有不同的访问修饰符。在这种情况下,是什么确定 ReadableData::getXprivate 而不是 public
  • @DanielH 实现来自DataData 是从私有继承而来。当您从某些东西继承时,所有数据/方法访问都被限制在继承级别。因此,如果您从 Data 私有继承,那么您从 Data 继承的所有内容现在都是私有的。实际的getX 函数来自Data,而不是来自IReadableData。那有意义吗?或者也许最后一点是问题所在;为什么访问级别是基于Data 而不是IReadableData
  • 是的,这就是我要问的问题。为什么访问级别来自Data 而不是IReadableData?您的评论和 StoryTeller 的回答似乎是因为实现来自Data;这是一个公平的经验法则吗?
猜你喜欢
  • 2011-01-05
  • 1970-01-01
  • 1970-01-01
  • 2011-03-11
  • 2011-01-23
  • 2011-08-10
  • 2021-11-25
相关资源
最近更新 更多