为啥具有私有构造函数的类不阻止从此类继承?如何控制哪些类可以从某个基类继承?

Posted

技术标签:

【中文标题】为啥具有私有构造函数的类不阻止从此类继承?如何控制哪些类可以从某个基类继承?【英文标题】:Why doesn't a class having private constructor prevent inheriting from this class? How to control which classes can inherit from a certain base?为什么具有私有构造函数的类不阻止从此类继承?如何控制哪些类可以从某个基类继承? 【发布时间】:2019-08-27 08:10:54 【问题描述】:
class B 
private:
    friend class C;
    B() = default;
;

class C : public B ;
class D : public B ;

int main() 
    C ;
    D ;
    return 0;

我假设由于只有类CB的朋友,并且B的构造函数是私有的,那么只有类C是有效的并且D不允许实例化B .但事实并非如此works。我的推理哪里错了,如何实现对允许哪些类继承某个基类的这种控制?

更新:正如 cmets 中的其他人所指出的那样,上面的 sn-p 按照我最初在 C++14 下的预期工作,但不是 C++17。在 main() 中将实例化更改为 C c; D d; 在 C++17 模式下也可以正常工作。

【问题讨论】:

看这个:***.com/questions/32235294/… @Diodacus:那又怎样,尽管在private: 部分中声明了私有构造函数,但将其声明为默认会使其公开? 我收到了您期望的错误:“'D::D(void)': 试图引用已删除的函数”(msvs 2017) @Stefan:我听到了,但是有两个类在语义上对 B 的子类是有意义的,我正试图在 C++ 中表达/实施这个逻辑约束。跨度> 我从未见过“恰好有两个类对子类 B 具有语义意义,而我正试图在 C++ 中表达/实施这种逻辑约束。”实际上在与未来的长期接触中幸存下来。一些未来的程序员,也许甚至你,会诅咒你没有看到他们明显需要另一个子类。 【参考方案1】:

这是添加到 C++17 的新功能。正在发生的事情是 C 现在被认为是一个聚合。由于它是一个聚合,它不需要构造函数。如果我们查看[dcl.init.aggr]/1,我们会发现聚合是

聚合是一个数组或一个类

没有用户提供的、显式的或继承的构造函数 ([class.ctor]),

没有私有或受保护的非静态数据成员(子句 [class.access]),

没有虚函数,并且

没有虚拟、私有或受保护的基类 ([class.mi])。

[ 注意:聚合初始化不允许访问受保护和私有基类的成员或构造函数。 — 尾注 ]

我们检查了所有这些要点。您没有在CD 中声明任何构造函数,所以有项目符号 1。您没有任何数据成员,因此第二个项目符号无关紧要,并且您的基类是公共的,因此第三个项目符号是满意。

在 C++11/14 和 C++17 之间发生的变化是聚合现在可以有基类。你可以看到旧的措辞here,它明确指出不允许使用基类。

我们可以通过检查 trait std::is_aggregate_v like 来确认这一点

int main()

    std::cout << std::is_aggregate_v<C>;

将打印 1。


请注意,由于CB 的朋友,您可以使用

C c;
C c1;
C c2 = C();
    

作为初始化C 的有效方法。因为D 不是B 的朋友,所以唯一有效的是D d;,因为那是聚合初始化。所有其他表单都尝试默认初始化,但由于D 已删除默认构造函数,因此无法完成。

【讨论】:

我猜想在B::B() = default;这样的类外写默认构造函数定义将被视为用户提供的构造函数,而在类中默认它被认为是非用户提供的构造函数? @VTT 这没什么区别。类内部的B() = default 仍然是用户声明的构造函数。 @NathanOliver 你确定吗?我很确定,在 cppcon 的众多演讲之一中,一位讲师解释了差异,尽管它可能应用于其他地方,而不是在这个例子中。找不到链接。 @Fureeish 100% 确定。将构造函数移出类确实会改变对象的初始化方式:***.com/questions/54350114/… @VioletGiraffe D d2(); 是最麻烦的解析,所以你有一个函数,而不是一个对象。【参考方案2】:

来自What is the default access of constructor in c++

如果类 X 没有用户声明的构造函数,则没有参数的构造函数被隐式声明为默认构造函数。隐式声明的默认构造函数是其类的内联公共成员。

如果类定义没有显式声明复制构造函数,则隐式声明。 [...] 隐式声明的复制/移动构造函数是其类的内联公共成员。

C 和 D 类的构造函数由编译器在内部生成。

顺便说一句:如果你想玩继承,请确保你定义了虚拟析构函数。

【讨论】:

我认为您误解了我的困惑点,尽管您的链接仍然相关。我知道每个CD 都有一个默认的公共构造函数,但是D 不应该能够实例化其基类B 的实例,因为后者的构造函数是私有的。 这能回答问题吗? 那么为什么B() = default; 被威胁为“没有用户声明的构造函数”?

以上是关于为啥具有私有构造函数的类不阻止从此类继承?如何控制哪些类可以从某个基类继承?的主要内容,如果未能解决你的问题,请参考以下文章

指向具有私有构造函数的类的类成员的指针

为啥没有构造函数参数的类需要括号

通过定义默认私有构造函数使类不可继承

为啥我的类不继承父类?

effective java学习笔记之不可实例化的类

如何为联合成员提供一个简单的类构造函数?