C++ public 接口的虚拟继承与实现的私有继承
C++ public virtual inhertiance of interface with private inheritance of implementation
我的问题是为什么下面的代码会这样运行。我不是在问设计的质量(我知道你们中的一些人会立即讨厌多重继承,我在这里不是在争论或反对它)我可以问一个单独的问题来了解作者试图做的事情实现,但让我们假设有与此等效的代码:-
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 年 "warning C4250: 'ReadableData': inherits 'Data::Data::getX' via dominance"
首先,我有点惊讶没有被告知 ReadableData 没有实现 getX(假设它的实现是私有的),但是没有出现警告并且我可以创建一个 ReadableData,但是尽管 ReadableData publicly继承自IReadableData,IReadableData的public个方法不可访问
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();应该可以访问。
编辑:我将示例代码稍微简化了一点,以便对意图有一点了解,但这并没有真正改变问题。
访问修饰符总是静态解析,而不是动态解析,即它基于静态而非动态类型。实现 IReadableData
的对象的契约严格来说是该对象的 pointer/reference 可以别名为 IReadableData
,然后可以有意义地调用方法 getX
它。毕竟,多态合约的全部意义在于以多态方式使用它们。对于直接使用派生对象时会发生什么,没有真正的保证,也没有必要保证。
因此,从这个意义上说,允许派生对象更改访问说明符,然后根据静态而非动态类型解析访问说明符,至少是一种与多态契约概念一致的选择.
综上所述,更改派生对象中的访问说明符无论如何都不是一个好主意。没有任何好处,因为它很容易变通,所以封装的好处为零,它只是暴露了这种奇怪的边缘情况。
在设计方面我并不是从根本上反对多重继承。然而,99% 的情况下,您最好避免使用钻石。同样是私有继承,几乎没有什么用处,枚举起来也比较简单:
- 针对空基class优化。
- 如果其他人编写了一个 class 具有虚函数的自定义技术,而您需要此 class 来实现您的。我说 "someone else" 是因为这种设计在更现代的 C++ 中很难证明是合理的,现在更容易传递 functions/lambda/
std::function
.
它是查找规则的衍生物,其中有一些与虚拟继承相关的要点。这个想法是虚拟继承不应该在名称查找期间引起歧义。所以在每个场景中都找到了不同的 getX()
声明。由于访问说明符仅在名称查找后检查(并且不影响名称解析),因此您可以遇到这样的障碍。
在 r2.getX();
的情况下,查找从 IReadableData
的上下文开始。由于立即找到声明,并且 IReadableData
没有基础,这就是查找解析的内容。它是 IReadableData
的 public 成员,因此您可以命名和调用它。之后就是动态调度机制负责调用支配Data::getX()
.
给出的实现
在 r.getX();
的情况下,查找工作不同。它从 ReadableData
的上下文开始。没有 getX()
的声明,因此转而查看 ReadableData
的直接基数。这里的事情变得有点不直观:
- 它检查
IReadableData
,并找到 IReadableData::getX()
。它记录了这个查找,以及它所在的 IReadableData
基础子对象。
- 它检查
Data
,并找到 Data::getX()
。此查找的注释,以及它在其中找到的 Data
基础子对象也被记录下来。
- 现在它尝试将#1 和#2 的查找集合并到
ReadableData
的查找集中。由于 #1 中的 IReadableData
子对象也是来自 #2 的 Data
子对象的子对象(由于虚拟继承),所有 #1 都被完全忽略。
- 在第 3 步中只剩下
Data::getX()
,查找解析为它。
所以 r.getX();
实际上是 r.Data::getX();
。这就是查找所发现的。正是在这一点上检查访问说明符。这是你错误的原因。
我所说的一切都是试图分解[class.member.lookup]部分在标准中描述的过程。我不想在这方面引用标准,因为我觉得用简单的英语解释发生的事情并没有多大用处。但您可以按照 link 阅读完整规范。
我的问题是为什么下面的代码会这样运行。我不是在问设计的质量(我知道你们中的一些人会立即讨厌多重继承,我在这里不是在争论或反对它)我可以问一个单独的问题来了解作者试图做的事情实现,但让我们假设有与此等效的代码:-
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 年 "warning C4250: 'ReadableData': inherits 'Data::Data::getX' via dominance"
首先,我有点惊讶没有被告知 ReadableData 没有实现 getX(假设它的实现是私有的),但是没有出现警告并且我可以创建一个 ReadableData,但是尽管 ReadableData publicly继承自IReadableData,IReadableData的public个方法不可访问
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();应该可以访问。
编辑:我将示例代码稍微简化了一点,以便对意图有一点了解,但这并没有真正改变问题。
访问修饰符总是静态解析,而不是动态解析,即它基于静态而非动态类型。实现 IReadableData
的对象的契约严格来说是该对象的 pointer/reference 可以别名为 IReadableData
,然后可以有意义地调用方法 getX
它。毕竟,多态合约的全部意义在于以多态方式使用它们。对于直接使用派生对象时会发生什么,没有真正的保证,也没有必要保证。
因此,从这个意义上说,允许派生对象更改访问说明符,然后根据静态而非动态类型解析访问说明符,至少是一种与多态契约概念一致的选择.
综上所述,更改派生对象中的访问说明符无论如何都不是一个好主意。没有任何好处,因为它很容易变通,所以封装的好处为零,它只是暴露了这种奇怪的边缘情况。
在设计方面我并不是从根本上反对多重继承。然而,99% 的情况下,您最好避免使用钻石。同样是私有继承,几乎没有什么用处,枚举起来也比较简单:
- 针对空基class优化。
- 如果其他人编写了一个 class 具有虚函数的自定义技术,而您需要此 class 来实现您的。我说 "someone else" 是因为这种设计在更现代的 C++ 中很难证明是合理的,现在更容易传递 functions/lambda/
std::function
.
它是查找规则的衍生物,其中有一些与虚拟继承相关的要点。这个想法是虚拟继承不应该在名称查找期间引起歧义。所以在每个场景中都找到了不同的 getX()
声明。由于访问说明符仅在名称查找后检查(并且不影响名称解析),因此您可以遇到这样的障碍。
在
r2.getX();
的情况下,查找从IReadableData
的上下文开始。由于立即找到声明,并且IReadableData
没有基础,这就是查找解析的内容。它是IReadableData
的 public 成员,因此您可以命名和调用它。之后就是动态调度机制负责调用支配Data::getX()
. 给出的实现
在
r.getX();
的情况下,查找工作不同。它从ReadableData
的上下文开始。没有getX()
的声明,因此转而查看ReadableData
的直接基数。这里的事情变得有点不直观:- 它检查
IReadableData
,并找到IReadableData::getX()
。它记录了这个查找,以及它所在的IReadableData
基础子对象。 - 它检查
Data
,并找到Data::getX()
。此查找的注释,以及它在其中找到的Data
基础子对象也被记录下来。 - 现在它尝试将#1 和#2 的查找集合并到
ReadableData
的查找集中。由于 #1 中的IReadableData
子对象也是来自 #2 的Data
子对象的子对象(由于虚拟继承),所有 #1 都被完全忽略。 - 在第 3 步中只剩下
Data::getX()
,查找解析为它。
所以r.getX();
实际上是r.Data::getX();
。这就是查找所发现的。正是在这一点上检查访问说明符。这是你错误的原因。- 它检查
我所说的一切都是试图分解[class.member.lookup]部分在标准中描述的过程。我不想在这方面引用标准,因为我觉得用简单的英语解释发生的事情并没有多大用处。但您可以按照 link 阅读完整规范。