嵌套类的值初始化

Posted

技术标签:

【中文标题】嵌套类的值初始化【英文标题】:Value initialization of nested classes 【发布时间】:2017-11-03 13:28:11 【问题描述】:

通过值初始化的规则。值初始化发生:

1,5) 当使用初始化器创建无名临时对象时 由一对空的括号或大括号组成(C++11 起);

2,6) 当具有动态存储持续时间的对象由 new-expression 的初始化器由一对空的 圆括号或大括号 (C++11 起);

3,7) 当一个非静态数据 成员或基类使用成员初始化器初始化 一对空的括号或大括号 (C++11 起);

4) 当一个命名的 变量(自动、静态或线程局部)用 由一对大括号组成的初始化器。

简单的例子

struct A
    int i;
    string s;
    A();
;

A a 
cout << a.i << endl // default initialized value

没有显式声明的构造函数并留下默认的默认ctor //编译器生成我们得到的一个。

struct A
    int i;
    string s;

;
A a;
cout << a.i << endl // zero-initialized value

但是使用另一个结构。

struct A
    int i;
    string s;

;

struct B
    A a;
    int c;
;

B a;
cout << a.a.i << endl // default initialized , even tho we did not , int struct B , declared A a.

a.i 的值是零初始化的,即使没有使用 / () 构造,这违反了规则(如果我没记错的话)。

在结构 B 上使用相同的逻辑:

struct A
    int i;
    string s;

;

struct B
    A a;
    int c;
;

B b;
cout << b.c << endl; // default inicialized

我们根据规则获得行为。

最后一个例子:

struct A

    int i;
    A()   
;

struct B  A a; ; 

std::cout << B().a.i << endl;

B().a.i 也是零初始化的,而我们显式声明了构造函数并且它没有被删除。

为什么这些值被初始化为零?根据here 规定的规则,它们应该是默认初始化而不是零初始化。

感谢您的回答。

【问题讨论】:

最后一个例子来自 cppreference (link)。并且它声明了它的零初始化。 我在最初的评论中很快就谈到了。在访问a.i 的第一个示例中是UB。第二个是聚合初始化,所以没关系。第三个也是聚合初始化,这样就可以了。在第四个例子中访问b.c 是UB。在第五个示例中,访问a.i 是可以的,因为您引用的第一条规则。 【参考方案1】:

A 是不是聚合的区别。

[dcl.init.aggr](强调我的)

聚合是一个数组或一个类(第 9 条),其中 没有用户提供的构造函数 (12.1),没有大括号或等号初始值设定项 对于非静态数据成员 (9.2),没有私有或受保护的非静态数据成员(第 11 条),

所以当A没有声明的构造函数时,说A a有aggregate initialization的效果

它将用一个空的初始化列表构造每个成员:

如果列表中的初始化子句少于聚合中的成员,则每个成员 没有显式初始化的应该从一个空的初始化列表初始化

所以你会得到intstd::string,它们会将整数成员值初始化为零。

当您提供默认构造函数时,聚合属性会丢失,int 成员保持未初始化,因此访问它被视为未定义行为。


具体来说:

此代码在访问a.i 时为未定义行为,因为您提供了用户定义的构造函数,因此int i 字段在构造后保持未初始化:

struct A
    int i;
    string s;
    A();
;

A a ;
cout << a.i << endl;

并且此代码在访问b.c 时表现出未定义的行为,因为您没有对B b 执行列表初始化:

struct B
    A a;
    int c;
;

B b;
cout << b.c << endl;

所有其他代码都可以,并将对整数字段进行零初始化。在您使用大括号 的场景中,您正在执行聚合初始化

最后一个示例有点棘手,因为您正在执行值初始化。由于B 是一个聚合,它被零初始化 ([dcl.init]),其中:

每个基类 子对象是零初始化的

所以你又可以访问A 子对象的整数成员了。

【讨论】:

我很确定A 是否有std::string 作为成员并不重要。无论哪种方式,它仍然是一个聚合(没有构造函数、私有/受保护的变量、没有基类、没有虚函数)。我意识到 OP 已经编辑了他们的问题:/ 好吧,仅供参考。 @Rakete1111:谢谢。我应该仔细检查一下。我认为拥有一个非聚合成员会使它失去聚合本身的资格。 (这仅适用于 POD)我更新了帖子以反映这一点。【参考方案2】:

根据聚合初始化的规则,成员确实是值初始化的。

值初始化:

在所有情况下,如果使用空的大括号 对并且 T 是聚合类型,则执行 聚合初始化 而不是值初始化。

聚合初始化的效果是:

    如果初始化子句的数量小于成员和基数(C++17 起)或初始化器列表完全为空,则剩余的成员和基数(C++17 起)根据通常的列表初始化规则(对非类类型和非具有默认构造函数的聚合类,以及聚合的聚合初始化)。如果引用类型的成员是这些剩余成员之一,则程序是错误的。

【讨论】:

以上是关于嵌套类的值初始化的主要内容,如果未能解决你的问题,请参考以下文章

go变量类的概念以及类的使用方式,嵌套结构体

使用 Flutter Provider 管理嵌套类的状态

Elastisearch不会通过带有动态类的嵌套调用返回数据

Core java for impatient 笔记

python 类的功能,字符串字节,嵌套等相关学习总结

在类中如何实现类的嵌套