析构函数必须只是可用的(公共)或完全有效的默认初始化类成员?



请考虑一个结构体A有一个U<R>类型的字段u,并且有一个默认初始化器。析构函数~U<R>只声明:

template<typename T>
struct U {
~U();
};
struct R;
struct A {
U<R> u = U<R>{};
};

所有的编译器都接受这个代码,demo: https://gcc.godbolt.org/z/oqMjTovMo

但是如果我们这样定义析构函数~U<R>:

template<typename T>
struct U {
~U() { static_assert( sizeof(T) > 0 ); }
};

则当前编译器发散。MSVC继续接受程序,而GCC/Clang打印错误

error: invalid application of 'sizeof' to an incomplete type 'R'

演示:https://gcc.godbolt.org/z/713TzPd6v

显然,编译器必须验证默认初始化类成员的析构函数可用性,以防在构造过程中发生异常。但是,标准是否要求编译器只检查析构函数的可用性(如MSVC所做的),或者编译器也应该检查析构函数的主体?MSVC的行为在这里看起来更方便,因为它允许在structA定义的时候前向声明R

p。这一探索不仅具有纯粹的理论意义。如果用std::unique_ptr代替这里的U,那么它就解释了为什么MSVC接受std::unique_ptr<R>类型的类字段,而GCC/Clang拒绝不完整的R类型的类字段。

[dcl.init.][qh]/8:(强调我的)

类类型的每个元素的析构函数可能被调用([class.dtor])从聚合初始化发生的上下文中。

[basic.def.odr]/8:

类的析构函数为odr-used如果可能被调用。

[basic.def.odr]/10:

每个程序应该只包含一个定义的所有非内联函数或变量在该程序中被丢弃的语句之外;不需要诊断。

所以~U()可能在A内部的U<R> u被调用,这需要它的定义。

然后根据[temp.inst]/4:

除非类模板或成员模板的成员是声明的特化,否则成员的特化是隐式实例化的当特化在需要成员定义存在的上下文中被引用时…

附加注意:当然,当A的实例被销毁时,它实际上会被调用。所以它需要被编译

注意:在第一个版本中,编译器接受代码,因为析构函数不是内联的,所以它在链接期间失败(示例)。

对于MSVC接受的代码,它似乎是一个bug。

相关内容

最新更新