将私有父类类型作为参数传递 (C2247)
Posted
技术标签:
【中文标题】将私有父类类型作为参数传递 (C2247)【英文标题】:Pass private parent class type as parameter (C2247) 【发布时间】:2019-02-26 13:38:08 【问题描述】:我有一个关于私有继承的小众问题。我也有这个问题的解决方案,但我不明白它为什么有效。
TL;DR
为什么某些中间级别的私有继承会阻止我将基类型作为参数传递给(私有)派生类?
考虑下面的代码(也可以在这里找到http://cpp.sh/3p5zv5):我有一个复合类型类,它可以包含指向它自己类型的子元素的指针。此外,该类包含模板方法 MyMethodTemplate(T value),允许任何类型的参数。我需要多次从这个类继承,这样 MyMethodTemplate(T) 不可用,而只能使用例如类型化版本 MyMethod() 调用整数,字符串,等等。
由于派生类将包含大量样板代码(此处未显示),我编写了从 cComposite 私有继承的类模板 cSpecializedComposite(成功隐藏了 MyMethodTemplate())。它的方法 MyMethod() 在内部从其父类调用 MyMethodTemplate()。到目前为止,一切顺利。
现在,为了摆脱最终用户代码中的模板参数,我想编写从模板公开继承的普通类(cSpecializedCompositeInt、cSpecializedCompositeString...)。我的假设是 cSpecializedCompositeInt 会知道 cSpecializedComposite 的接口,但不知道它的内部。在 cSpecializedCompositeInt 的构造函数中,我可以选择传递一个 unique_ptr 的向量,该向量将传递给它的父构造函数(天知道用它做什么,这里没什么可看的,继续前进)。请注意,cComposite 的类定义对 cSpecializedCompositeInt 是可见的,即使 cSpecializedCompositeInt 没有从它继承,据它所知。
但是,我在 cSpecializedCompositeInt 的构造函数中收到编译器错误 C2247,告诉我不能使用 cComposite,因为 cSpecializedComposite 是从它私下继承的。这发生在 msvc10 和 GCC 4.9.2(http://cpp.sh 后面的编译器)上。
将私有继承更改为受保护允许 cSpecializedCompositeInt 知道它是从 cComposite 间接继承的,并且编译器错误消失了。
这与Private Inheritance and Derived Object to Base reference 有多大关系?
#include <vector>
#include <memory>
class cComposite
public:
cComposite(std::vector<std::unique_ptr<cComposite>>&& vecC)
: m_vecC(std::move(vecC))
//empty
template <typename T>
void MyTemplateMethod(T value)
//do something with any type of value
private:
std::vector<std::unique_ptr<cComposite>> m_vecC;
;
template <typename MySpecialType>
class cSpecializedComposite : private cComposite
public:
cSpecializedComposite(std::vector<std::unique_ptr<cComposite>>&& vecC)
: cComposite(std::move(vecC))
//empty
void MyMethod(MySpecialType value)
//allow only MySpecialType as Input, call base class template method to do something
cComposite::MyTemplateMethod(value);
;
class cSpecializedCompositeInt : public cSpecializedComposite<int>
public:
cSpecializedCompositeInt(std::vector<std::unique_ptr<cComposite>>&& vecC)
: cSpecializedComposite(std::move(vecC))
//empty
;
int main(int argc, char* argv[])
std::vector<std::unique_ptr<cComposite>> vecC;
cSpecializedCompositeInt spec(std::move(vecC));
spec.MyMethod(5);
return 0;
【问题讨论】:
因为私有继承意味着私有。继承的一切都是私有的。甚至是私有继承类的基类,无论它们是否是公共的。 我想的也差不多。我的问题是,为什么基类实际上对派生类不可见,而不是仅仅阻止它们知道它们从谁继承,也就是使内部接口对它们不可用? 因为private
使类可以随时更改其私有继承的对象,并且破坏任何 API 的风险为 0%。由于除了类之外没有其他人知道它的私有继承来源,因此可以随时更改类的私有继承。它甚至可以完全删除。因此,只有类本身需要更改。与其从基类公开继承,不如考虑使用虚拟公共继承。现在,可以从任何子类访问虚拟公共类。
谢谢,这个理由对我来说很有意义。但是,我仍然可以在我的最终用户代码中构造和使用 cSpecializedComposite此站点上反复出现的主题之一是请求Minimal, Complete, and Verifiable example。你在“完整”和“可验证”部分做得很好,但在“最小”部分做得很好。请允许我简化您的代码以删除可能分散注意力的细节。
// Base class, constructed from a type that involves itself.
class A
public:
A(A *)
;
// Intermediate class, derives privately from the base class.
class B : private A
public:
B(A * a) : A(a)
;
// Most derived class, same constructor parameter as the base class.
class C : public B
public:
C(A * a) : B(a) /// error: 'class A A::A' is inaccessible
;
int main(int argc, char* argv[])
return 0;
注意缺少模板和向量;这些只是红鲱鱼。另外,很抱歉使用原始指针;它们只是以最少的开销/行李来介绍问题的便捷方式。 (我会使用引用,但这会将其中一个构造函数变成一个复制构造函数,这感觉很不明智。)
看B
的构造函数的定义。在该定义中,“A
”被使用了两次:一次作为参数类型的一部分,一次在初始化列表中。对于后一种情况,A
的使用肯定是指正在构造的类的(私有)基类。问题:为什么编译器应该假设前一种情况不也引用私有基类? (如果私有基类被更改,参数的类型是否也必然会更改?编译器假定“是”,但您可以在A
和B
之间引入另一个中间类,它可能会保留参数的类型。)
据我所知(我没有仔细检查语言规范),当您处于B
的上下文中时,任何提及其私有基类的内容都被视为私有信息。您可以将构造函数的声明视为:B(<private> * a)
。由于C
不允许知道B
的私有信息,所以不允许调用这个构造函数。它根本无法匹配参数列表,就像参数的类型是在B
的private
部分中定义的一样。幸运的是,可以通过从参数列表中删除“A
”来避免这种情况。
在下文中,唯一的变化是引入和使用typedef
。
class A;
typedef A * special_type;
// Base class, constructed from a type that *indirectly* involves itself.
class A
public:
A(special_type)
;
// Intermediate class, derives privately from the base class.
class B : private A
public:
B(special_type a) : A(a)
;
// Most derived class, same constructor parameter as the base class.
class C : public B
public:
C(special_type a) : B(a) /// no error!
;
int main(int argc, char* argv[])
return 0;
在您的情况下,这将具有为符号丰富的std::vector<std::unique_ptr<cComposite>>
引入较短同义词的附带好处。
【讨论】:
感谢您的回答。我发现您可以通过引入 typedef 来规避问题,这令人难以置信,就像我仍然发现 C 不能使用公开可用的类型进行 B 父类初始化一样令人难以置信,但其他任何人构造一个B实例即可。 @Daniel 这是一个语法问题,而不是语义问题。 C 可以使用公开可用的类型来初始化 B。问题是 B 不能在不使用 typedef 的情况下表示所需参数是公开可用的类型。以上是关于将私有父类类型作为参数传递 (C2247)的主要内容,如果未能解决你的问题,请参考以下文章