将私有父类类型作为参数传递 (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,在其构造函数中传递 cComposite,尽管模板类是从 cComposite 私有继承的。使派生类完全无法访问私有基类似乎有点奇怪,但其他人(即最终用户)没有。我敢肯定它背后有一个基本原理,我可以让它休息,但它仍然看起来很奇怪。 【参考方案1】:

此站点上反复出现的主题之一是请求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 的使用肯定是指正在构造的类的(私有)基类。问题:为什么编译器应该假设前一种情况不也引用私有基类? (如果私有基类被更改,参数的类型是否也必然会更改?编译器假定“是”,但您可以在AB 之间引入另一个中间类,它可能会保留参数的类型。)

据我所知(我没有仔细检查语言规范),当您处于B 的上下文中时,任何提及其私有基类的内容都被视为私有信息。您可以将构造函数的声明视为:B(&lt;private&gt; * a)。由于C不允许知道B的私有信息,所以不允许调用这个构造函数。它根本无法匹配参数列表,就像参数的类型是在Bprivate 部分中定义的一样。幸运的是,可以通过从参数列表中删除“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&lt;std::unique_ptr&lt;cComposite&gt;&gt; 引入较短同义词的附带好处。

【讨论】:

感谢您的回答。我发现您可以通过引入 typedef 来规避问题,这令人难以置信,就像我仍然发现 C 不能使用公开可用的类型进行 B 父类初始化一样令人难以置信,但其他任何人构造一个B实例即可。 @Daniel 这是一个语法问题,而不是语义问题。 C 可以使用公开可用的类型来初始化 B。问题是 B 不能在不使用 typedef 的情况下表示所需参数是公开可用的类型。

以上是关于将私有父类类型作为参数传递 (C2247)的主要内容,如果未能解决你的问题,请参考以下文章

将类型函数作为Dart中的参数传递

如何将匿名类型作为参数传递?

vue搜索框中如何根据子类找到父类,并把子类和父类作为参数传递

Swift:将类型作为参数传递

如何将注释类型作为方法参数传递?

将模板化智能指针类型作为模板参数传递