C++中对象间的相互引用

Mutual referencing among objects in 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;
};

现在所有编译器都接受该程序,演示:https://gcc.godbolt.org/z/bs17xfxEs

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

B z{ z.a2, z.a1 }; 尝试复制构造 a1a2,而不是使用 z.a2z.a1 作为第一个字段聚合初始化它们。1

B z{{z.a2, 0}, {z.a1, 0}}; 适用于 GCC 和 Clang。 MSVC 给出 error C2078: too many initializers,这看起来像一个错误。


1 这里,直接列表初始化是为 z 执行的,在这种情况下为每个成员解析为 aggregate initialization, which in turn performs copy-initialization,并且:

[dcl.init.general]/15.6.2

... if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered.

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