【问题标题】:Mutual referencing among objects in C++C++中对象之间的相互引用
【发布时间】:2021-09-26 08:39:21
【问题描述】:

我想创建两个具有相互成员引用的对象。稍后它可以扩展到例如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

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

【问题讨论】:

  • const A &a 是一个问题,因为该字段必须初始化(它是 const),并且默认 ctor 无法为其提供任何好的值,因此被删除。

标签: c++ language-lawyer c++20


【解决方案1】:

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,看起来像个bug。


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

[dcl.init.general]/15.6.2

...如果是复制初始化,其中源类型的 cv 非限定版本与目标类相同或派生类,则考虑构造函数。

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

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2017-11-04
  • 2012-07-21
  • 1970-01-01
  • 2014-06-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-16
相关资源
最近更新 更多