Mar*_*ark 7 c++ one-definition-rule language-lawyer implicit-declaration virtual-destructor
在思考这个问题时,我偶然发现了我不了解的其他内容。
标准说...
如果类没有用户声明的析构函数,则将析构函数隐式声明为默认值。隐式声明的析构函数是其类的内联公共成员。
[...]如果类具有带有虚拟析构函数的基类,则其析构函数(无论是用户声明的还是隐式声明的)都是虚拟的。
默认使用但未定义为deleted的析构函数在使用odr时或在其首次声明后被明确默认为隐式定义。
[...]如果虚拟成员函数不是纯函数,则将被使用。[...]
所以现在我想知道这段代码是否应该编译:
#include <memory>
struct Base {
virtual ~Base() = default;
};
struct Bar;
struct Foo : Base {
std::unique_ptr<Bar> bar_{};
};
Run Code Online (Sandbox Code Playgroud)
我认为~Foo()必须对其进行隐式定义,因为它是虚拟的,但由于Bar在此TU中不完整,因此无法编译。但是代码可以在所有主要的编译器中编译。
我想念什么?
好吧,既然这个问题已经通过各种答案和评论得到澄清,那么让我再一次回答这个问题。参考 C++17 标准。
OP代码的问题是它Foo有一个不纯的虚拟析构函数,并且所有非纯虚拟函数都隐式使用odr([basic.def.odr]/3)。由于析构函数是 odr 使用的,因此实现似乎必须生成一个定义 ([class.dtor]/7),并且该定义必须导致 的实例化std::default_delete<Bar>::operator(),这使得程序格式不正确,因为Bar不完整 ([unique .ptr.dltr.dflt]/4)。那么为什么编译器不产生诊断信息呢?
我认为 [class.dtor]/7 的措辞存在问题,其内容如下,并且迄今为止在较新的标准版本中尚未进行实质性更改:
默认且未定义为已删除的析构函数在使用 odr (6.2) 或在第一次声明后显式默认时会被隐式定义。
的隐式定义到底是什么时候Foo::~Foo生成的?它是在“当”Foo::~Foo使用 odr 时生成的。但是...什么时候使用Foo::~Fooodr-?
显然“何时”并不是指物理时间,可能意味着“哪里”之类的意思。这大概意味着:
但是,在虚拟析构函数的隐式 odr 使用的情况下,纯粹由于它是虚拟的而发生,那么它在哪里使用呢?解释它的一种方法是,在Foo出现类定义的每个翻译单元中,Foo::~Foo都被视为在该翻译单元中使用了 odr,并且编译器的行为必须就像在该翻译单元中生成定义一样。如果是这种情况,那么您的程序需要诊断。
另一种解释方法是,由于标准没有定义隐式 odr 使用发生的任何位置,因此它被认为是未指定的,并且实现可以选择在哪里定义析构函数,甚至根本不定义。(也就是说,如果“何时”表示“何处”,而“何处”表示“在 odr 使用发生的一组位置中”,则未指定该组实际上是什么,并且可能为空。)在实践中,编译器可能在翻译单元中定义析构函数,其中“由 vtable 使用 odr”(我将其放在引号中,因为从技术上讲,标准没有定义 vtable 的概念,因此 vtable 不能真正使用任何东西) 并且 vtable 被某些表达式“odr 使用”,例如构造函数的潜在调用、dynamic_cast、typeid以及涉及 的异常处理Foo。如果您不执行任何这些操作,编译器不会在该 TU 中隐式定义虚拟析构函数。
我认为应该修改标准措辞,以便将现有实践编入法典,即标准应该说明未指定虚拟函数的隐式 odr 使用发生的位置。(此外,如果实现确实确定答案是“无处”,则不应允许将程序视为 [basic.def.odr]/4 下的格式不正确的 NDR。这将是不正当的。)