c++ - 必须公开聚合字段构造函数才能在 C++ 中使用聚合初始化吗?
问题描述
请考虑具有带有私有构造函数B
的类字段的聚合结构的代码:A
class A { A(int){} friend struct B; };
struct B { A a{1}; };
int main()
{
B b; //ok everywhere, not aggregate initialization
//[[maybe_unused]] B x{1}; //error everywhere
[[maybe_unused]] B y{}; //ok in GCC and Clang, error in MSVC
}
我的问题是关于B
. 由于初始化是代表调用代码(main
此处的函数)进行的,我希望编译器必须拒绝它,因为A
' 的构造函数是私有的。事实上B{1}
,所有编译器的构造都失败了。
但令我惊讶的B{}
是,GCC 和 Clang 都接受了该构造,演示:https ://gcc.godbolt.org/z/7851esv6Y
并且只有 MSVC 以错误拒绝它error C2248: 'A::A': cannot access private member declared in class 'A'
。
是 GCC 和 Clang 中的错误,还是标准允许他们接受此代码?
解决方案
...与聚合结构
B
...
为了完整起见,让我们首先注意根据[dcl.init.aggr]/1(N4861(2020 年 3 月布拉格后工作草案/C++20 DISB
)确实是 C++14 到 C++20 中的聚合)):
聚合是一个数组或一个类 ([class])
- (1.1)没有用户声明或继承的构造函数([class.ctor]),
- (1.2) 没有私有或受保护的直接非静态数据成员 ([class.access]),
- (1.3) 没有虚函数 ([class.virtual]),并且
- (1.4) 没有虚拟、私有或受保护的基类 ([class.mi])。
而在 C++11 中,由于不违反non-static data members 的大括号或等式初始化器B
,而被取消资格作为聚合,这是在 C++14 中删除的要求。
因此,根据[dcl.init.list]/3 B x{1}
并且B y{}
都是聚合初始化:
类型的对象或引用的列表初始化
T
定义如下:
- [...]
- (3.4) 否则,如果
T
是聚合,则执行聚合初始化 ([dcl.init.aggr])。
对于前一种情况,根据[dcl.init.aggr]/3B x{1}
, 的数据成员a
是聚合B
的显式初始化元素。这意味着,根据[dcl.init.aggr]/4,特别是/4.2,数据成员是从初始化子句复制初始化的,这需要在聚合初始化的上下文中构造一个临时对象,使程序格式错误,因为匹配的构造函数是私有的。A
A
B x{1}; // needs A::A(int) to create an A temporary
// that in turn will be used to copy-initialize
// the data member a of B.
如果我们在初始化子句中使用一个A
对象,则不需要A
在聚合初始化的上下文中访问私有构造函数,并且程序是良构的。
class A {
public:
static A get() { return {42}; }
private:
A(int){}
friend struct B;
};
struct B { A a{1}; };
int main() {
auto a{A::get()};
[[maybe_unused]] B x{a}; // OK
}
对于后一种情况,B y{}
根据[dcl.init.aggr]/3.3,数据成员a
不再B
是聚合的显式初始化元素,并且根据[dcl.init.aggr]/5,特别是/5.1
对于非联合聚合,不是显式初始化元素的每个元素都按如下方式初始化:
- (5.1) 如果元素具有默认成员初始化程序 ([class.mem]),则从该初始化程序初始化该元素。
- [...]
并且数据成员a
是B
从其默认成员初始化程序初始化的,这意味着A::A(int)
不再从无法访问的上下文中访问私有构造函数。
最后是私有析构函数的情况
如果我们添加私有析构函数,
A
那么所有编译器都会以正确的错误证明它:
由[dcl.init.aggr]/8 [强调我的] 管理:
类类型的每个元素的析构函数可能从发生聚合初始化的上下文中调用([class.dtor]) 。[注意:此规定确保可以为完全构造的子对象调用析构函数,以防抛出异常([except.ctor])。——尾注]
推荐阅读
- python - 无法在 Python Selenium 中使用显式等待?
- python-3.x - Pandas 使用列表中的 ColumnNames 创建 DataFrame
- go - Go Gin 中间件事件
- oracle - 金门复制极度延迟
- c++ - AES-128 CFB-8解密前16字节损坏
- android - 将标记方向更改为特定点
- reactjs - 为什么标准的 Web React MS Bot Framework 实现会删除自己的或用户的消息?
- python - AttributeError:“列表”对象属性“索引”是只读的
- vue.js - Laravel VUE JS 将选择数据绑定到另一个元素
- maven - 是否可以以编程方式更改 Maven 插件中的依赖项?