初始化相互引用的对象

Cub*_*bbi 19 c++ language-lawyer

考虑以下一对相互引用类型:

struct A;
struct B { A& a; };
struct A { B& b; };
Run Code Online (Sandbox Code Playgroud)

这可以通过GCC,Clang,Intel,MSVC中的聚合初始化来初始化,但不能通过SunPro来初始化,因为SunPro坚持要求用户定义的ctors.

struct {A first; B second;} pair = {pair.second, pair.first};
Run Code Online (Sandbox Code Playgroud)

这个初始化合法吗?

更详细的演示:http://ideone.com/P4XFw

现在,听从Sun的警告,用户定义的构造函数的类怎么样?以下适用于GCC,clang,Intel,SunPro和MSVC,但它是否合法?

struct A;
struct B { A& ref; B(A& a) : ref(a) {} };
struct A { B& ref; A(B& b) : ref(b) {} };

struct {B first; A second;} pair = {pair.second, pair.first};
Run Code Online (Sandbox Code Playgroud)

演示:http://ideone.com/QQEpA

最后,如果容器也不是微不足道的,例如(在G ++,Intel,Clang(带警告)中工作,但不是MSVC(初始化器中未知的"配对")或SunPro("配对不是结构")

std::pair<A, B> pair(pair.second, pair.first);
Run Code Online (Sandbox Code Playgroud)

从我所看到的情况来看,§3.8[basic.life]/6禁止在生命周期开始之前访问非静态数据成员,但是对于第二次"对"第二次"访问"的左值评估是什么?如果是,那么这三个初始化都是非法的吗?另外,§8.3.2[dcl.ref]/5说"引用应该被初始化以引用一个有效的对象",这可能使所有三个都是非法的,但也许我错过了一些东西,编译器接受这个是有原因的.

PS:我意识到这些课程在任何方面都不实用,因此语言律师标签.这里相关且稍微更实际的旧讨论:C++中没有指针的循环引用

Oli*_*ver 1

一开始这让我很困惑,但我想我现在明白了。根据 1998 标准 12.6.2.5,C++ 保证数据成员按照它们在类中声明的顺序进行初始化,并且构造函数主体在所有成员初始化后执行。这意味着表达式

struct A;
struct B { A& a; };
struct A { B& b; };
struct {A first; B second;} pair = {pair.second, pair.first};
Run Code Online (Sandbox Code Playgroud)

这是有道理的,因为pair是一个自动(本地,堆栈)变量,因此编译器知道它的相对地址和成员地址,并且第一个和第二个没有构造函数。

为什么这两个条件意味着上面的代码有意义:当构造first类型为 的时A(在 的任何其他数据成员之前pair),first的数据成员b设置为引用pair.second,编译器知道其地址,因为它是堆栈变量(程序中已存在空间,AFAIU)。请注意,pair.second作为一个对象,即内存段,尚未初始化(包含垃圾),但这并没有改变该垃圾的地址在编译时已知并且可用于设置引用的事实。由于A没有构造函数,因此它无法尝试对 执行任何操作b,因此行为已明确定义。一旦first初始化,就轮到second,同样:它的数据成员a引用pair.first,其类型为Apair.first地址是编译器已知的。

如果编译器不知道地址(比如因为通过 new 运算符使用堆内存),则应该出现编译错误,或者如果不是,则出现未定义的行为。尽管明智地使用新的放置操作符可能会允许它工作,但从那时起,在初始化时可以知道first和的地址。secondfirst

现在来说说变化:

struct A;
struct B { A& ref; B(A& a) : ref(a) {} };
struct A { B& ref; A(B& b) : ref(b) {} };
struct {B first; A second;} pair = {pair.second, pair.first};
Run Code Online (Sandbox Code Playgroud)

与第一个代码示例的唯一区别是B构造函数是显式定义的,但汇编代码肯定是相同的,因为构造函数主体中没有代码。因此,如果第一个代码示例有效,那么第二个代码示例也应该有效。

然而,如果B 的构造函数主体中pair.second代码,它正在获取对尚未初始化的对象 ( ) 的引用(但其地址已定义且已知),并且该代码使用a,那么很明显您是寻找麻烦。如果幸运的话,您会遇到崩溃,但是写入a可能会默默失败,因为当最终A调用构造函数时,这些值会被覆盖。的