c++中对象之间的相互引用



我想创建两个对象,它们之间有相互的成员引用。之后,它可以扩展为例如N个引用对象的闭环,其中N在编译时是已知的。

最初的尝试是使用最简单的结构体A,它没有任何构造函数,这使得它成为一个聚合体(v模拟了一些负载):

struct A {
const A & a;
int v = 0;
};
struct B {
A a1, a2;
};
consteval bool f()
{
B z{ z.a2, z.a1 };
return &z.a1 == &z.a2.a;
}
static_assert( f() );

不幸的是,它不被编译器接受,因为错误:

accessing uninitialized member 'B::a2'

实际上很奇怪,因为没有真正的读访问,只是记住它的地址。演示:https://gcc.godbolt.org/z/cGzYx1Pea

A中加入构造函数后,问题得到了解决,使其不再聚合:

struct A {
constexpr A(const A & a_) : a(a_) {}
constexpr A(const A & a_, int v_) : a(a_), v(v_) {}
const A & a;
int v = 0;
};

现在所有的编译器都接受这个程序,demo: https://gcc.godbolt.org/z/bs17xfxEs

令人惊讶的是,看似相等的程序修改使其有效。在这种情况下,标准中是否真的有一些措辞阻止了聚合的使用?究竟是什么让第二个版本安全且被接受?

B z{ z.a2, z.a1 };尝试复制构建a1a2,而不是将z.a2,z.a1作为第一字段进行聚合初始化1

B z{{z.a2, 0}, {z.a1, 0}};可以在GCC和Clang中使用。MSVC给出了error C2078: too many initializers,看起来像一个bug。


1这里,对z执行直接列表初始化,在这种情况下,它解析为聚合初始化,然后对每个成员执行复制初始化,并且:

[dcl.init.general]/15.6.2

…如果是复制初始化,源类型的cv不限定版本与目标类相同,或者是目标类的派生类,则需要考虑构造函数。

因此,由于初始化式z.a2z.a1与对应的成员具有相同的类型,因此忽略了成员的聚合性,并使用复制构造函数。

最新更新