【问题标题】:Why does default initialization of a class field in C++ require destructor invocation?为什么 C++ 中类字段的默认初始化需要调用析构函数?
【发布时间】:2021-10-10 20:06:06
【问题描述】:

请帮我完成这个程序:

struct U { 
    U(int *) noexcept;
private:
    ~U() noexcept;
};

struct B {
    B();
    ~B();
    U v; //ok
    U w{nullptr}; //ok
    U u = nullptr; //error
};

这里 struct U 有一个私有析构函数,仅用于证明编译器并未真正调用析构函数并简化代码长度。

而structB只声明了一个默认的构造函数和析构函数,所以编译器不会在这个翻译单元中生成它们。

struct B 也有 3 个字段:vwuvw 字段没有问题,但对于 u 字段,编译器会发出关于 U 的不可访问析构函数的错误:

error: 'U::~U()' is private within this context
   13 |     U u = nullptr; //error

演示:https://gcc.godbolt.org/z/YooGe9xq6

问题是:

  1. 如果B::B() 没有在这个翻译单元中编译,为什么要考虑字段默认初始化?
  2. 错误是因为为u 字段的初始化创建了一个临时对象吗? (没有强制复制省略?)
  3. 那么uw 的情况有什么区别?

【问题讨论】:

  • 请注意,强制copy elision 不能发生,因为它要求析构函数在复制发生时可访问。请参阅 this question 了解有关为什么会这样的一些信息。
  • 我愿意将此诊断归因于编译器特性。例如,iccmsvc 都拒绝编译代码,即使没有违规行。总体而言,无论U 成员如何初始化,B 都无法使用。

标签: c++ default-value


【解决方案1】:

如果 B::B() 没有在这个翻译单元中编译,为什么要考虑字段默认初始化?

因为成员初始化是类定义的一部分。因此,类定义本身是无效的,而不必涉及其构造函数的定义。在标准中,您可以从[class.mem.general] 开始,一直到brace-or-equal-initializer,这最终需要一个有效的“沼泽标准”赋值表达式。

错误是因为为初始化 u 字段创建了一个临时对象吗? (没有强制复制省略?)

是的,你的godbolt链接中的编译器错误很清楚:

<source>:13:11: error: temporary of type 'U' has private destructor
    U u = nullptr; //error

请参阅Why is public destructor necessary for mandatory RVO in C++?,了解为什么强制复制省略不适用(感谢@NathanPierson!)

那么 u 和 w 的情况有什么区别呢?

w 直接初始化而无需事先创建临时对象,因此 creating 它工作正常,因为表达式中没有任何内容试图调用 U 的析构函数。显然,B::~B() 的任何定义在 w 上仍然会失败,但这已经超出了重点。

【讨论】:

  • 脚注:我没有足够的信心做出这部分答案,但据我所知,在类的定义是古老的“C++ 不能脱离上下文解析”的难题。并且添加特定的上下文异常wrt /保护级别和其他东西会很快变得一团糟。有人请告诉我这是不是基地。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-01-18
  • 2020-05-01
  • 2011-06-22
  • 2018-07-17
  • 2020-11-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多