我的问题是为什么以下代码的行为与它一样。我不是在询问设计的质量(我知道你们中的一些人会立即讨厌多重继承,我不是在这里争论或反对)我可以问一个单独的问题,该问题涉及作者试图实现,但我们假设存在与此等效的代码: -
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();应该是可以访问的。
编辑:我使示例代码略微不那么简单,只是对意图有一点了解,但它并没有真正改变这个问题。
答案 0 :(得分:3)
它是查找规则的衍生物,它具有一些与虚拟继承相关的项目符号。这个想法是虚拟继承不应该在名称查找过程中引起歧义。因此,在每个场景中都会找到getX()
的不同声明。由于访问说明符仅在名称查找后检查(并且不影响名称解析),因此您可以遇到此类问题。
在r2.getX();
的情况下,查询从IReadableData
的上下文开始。由于立即发现声明,并且IReadableData
没有基础,因此这是查找解析的内容。它是IReadableData
的公共成员,因此您可以命名并调用它。在那之后,它是动态调度机制,负责调用由支配Data::getX()
给出的实现。
对于r.getX();
,查找的工作方式不同。它始于ReadableData
的上下文。没有getX()
的声明存在,所以转向查看ReadableData
的直接基础。这里的事情有点不直观:
IReadableData
,找到IReadableData::getX()
。它注意到这个查找,以及它所在的IReadableData
基础子对象。Data
,找到Data::getX()
。还记下了此查找的注释以及它所在的Data
基础子对象。ReadableData
的查找集中。由于#1中的IReadableData
子对象也是来自#2的Data
子对象的子对象(由于虚拟继承),所以#1都被完全忽略。 / LI>
Data::getX()
,查找将解析为它。
所以r.getX();
实际上是r.Data::getX();
。查找发现的是什么。此时检查访问说明符。这是导致错误的原因。
我所说的一切都是试图打破[class.member.lookup]部分标准中描述的过程。我并不想引用这个标准,因为我觉得它并没有用于解释用简单英语发生的事情。但您可以按照链接阅读完整的规范。
答案 1 :(得分:1)
访问修饰符始终是静态解析,而不是动态解析,即它基于静态而非动态类型。实现IReadableData
的对象的合同严格来说,指向该对象的指针/引用可以别名为IReadableData
,然后可以在其上有意义地调用方法getX
。毕竟,多态契约的全部意义在于多态地使用它们。关于直接使用派生对象时会发生什么,没有真正的保证或必要性。
因此,从这个意义上说,允许派生对象更改访问说明符,然后根据静态类型而不是动态类型解析访问说明符,至少是一个与多态契约概念一致的选择。 / p>
所有这一切,在任何方面改变派生对象中的访问说明符并不是一个好主意。没有上升空间,因为解决它是微不足道的,所以没有封装优势,只是暴露了这个奇怪的边缘情况。
设计明智我并不是从根本上反对多重继承。然而,99%的情况下,钻石是你最好避免的东西。私有继承也几乎没有用,并且它们更容易枚举:
std::function
。