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

请考虑结构A具有u类型的U<R>和默认初始值设定项。析构函数~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行为在这里看起来更方便,因为它允许在结构A定义的时刻前向声明R

附注:这项探索不仅仅具有理论意义。如果将这里的U替换为std::unique_ptr,那么它解释了为什么std::unique_ptr<R>类型的类字段被msvc接受,而被GCC/Clang拒绝,因为类R不完整。


解决方案

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

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

[basic.def.odr]/8:

类的析构函数如果可能被调用,则为ODR使用。

[basic.def.odr]/10:

每个程序应包含该程序中ODR使用的ODR语句之外的每个非内联函数或变量的一个定义;不需要诊断。

因此~U()可能在U<R> u内部调用,这需要其定义。

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

除非类模板或成员模板的成员是声明的专门化,否则当在要求存在成员定义的上下文中引用专门化时,该成员的专门化是隐式实例化的...

附加说明:当然,当A的实例被销毁时,实际上是invoked。因此需要对其进行编译。

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

至于MSVC接受代码,它似乎是一个错误。

相关文章