C++花括号初始化私有构造对象未报编译错误的原因
复现代码
class DoorKeeper; struct Door { class Key { Key() = default; // ~Key() = default; // [1] friend DoorKeeper; }; void open(Key) {} }; int main(int, char **) { Door().open(Door::Key()); // 始终报构造函数私有错误 Door().open(Door::Key{}); // 仅取消注释[1]或新增成员时报错 }
行为差异原因
两种写法的行为不一致,是C++聚合初始化规则、特殊成员函数访问权限规则共同作用的结果,两种初始化语法走的是完全不同的执行路径:
1. 两种初始化的执行路径本质不同
- 圆括号写法
Door::Key()属于值初始化:对于类类型,值初始化流程会直接查找并调用类的默认构造函数,这一步必然会检查构造函数的访问权限。代码中Key的默认构造函数声明在私有区域,main函数不是Key的友元,无权限访问私有构造函数,因此这行代码永远会触发构造函数私有的编译错误。 - 花括号写法
Door::Key{}在Key满足聚合类条件时,会走聚合初始化路径:聚合初始化是逐成员初始化对象的成员,整个流程不会调用类的构造函数,因此哪怕构造函数是私有的,只要流程中没有用到该构造函数,就不会触发权限检查。
2. 默认状态下花括号初始化可通过编译的原因
在注释掉[1]行析构函数、未新增类成员的状态下,Key完全符合C11~C17标准中聚合类的判定要求:
- 没有用户提供的构造函数:代码中
Key() = default;是在首次声明时要求编译器生成默认构造,不属于用户手动提供实现的构造函数,不违反聚合类要求 - 没有私有/保护的非静态数据成员:Key是空类,不存在非静态数据成员
- 无基类、无虚函数
此时Door::Key{}执行聚合初始化时,因为类没有需要初始化的非静态成员,整个流程完全不涉及对私有默认构造函数的调用,不会触发构造函数权限报错。同时该状态下Key的析构函数是编译器隐式生成的,隐式生成的特殊成员函数默认是public访问权限,临时对象销毁时调用public析构函数完全合法,因此整行代码可以正常通过编译。
3. 取消注释析构函数、新增成员时花括号初始化报错的原因
两种报错场景的触发逻辑不同:
- 取消注释[1]行
~Key() = default;时:class定义内的成员默认访问权限为private,该析构函数是显式声明在私有区域的私有成员,main函数不是Key的友元,无权限访问私有析构函数,而临时对象生命周期结束时必须调用析构函数,因此会触发析构函数私有的编译错误。此外C++20标准调整了聚合类判定规则,要求聚合类不能有用户声明的构造函数,此时Key因显式声明了默认构造函数不再属于聚合类,花括号初始化会转而尝试调用私有默认构造函数,同样会触发编译错误。 - 为Key类新增成员时:如果新增的非静态成员写在类的默认私有区域(新增成员默认落在class开头的private段),会导致Key出现私有非静态数据成员,不再满足聚合类要求,花括号初始化会转而尝试调用私有默认构造函数,触发构造函数权限报错。如果新增的是public成员,Key仍然属于聚合类,花括号初始化不会触发构造函数权限检查。
内容的提问来源于stack exchange,提问作者OznOg
相关产品推荐
相关产品推荐

