7

在思考这个问题时,我偶然发现了一些我不明白的东西。

标准说...

[class.dtor]/4

如果一个类没有用户声明的析构函数,则析构函数被隐式声明为默认值。隐式声明的析构函数是其类的内联公共成员。

[class.dtor]/10

[...] 如果一个类有一个带有虚拟析构函数的基类,那么它的析构函数(无论是用户声明的还是隐式声明的)都是虚拟的。

[class.dtor]/7

默认且未定义为已删除的析构函数在使用 odr 或在其第一次声明后显式默认时隐式定义。

[basic.def.odr]/3

[...]如果它不是纯的,则虚拟成员函数是 odr-used 的。[...]

所以现在我想知道这段代码是否应该编译:

#include <memory>

struct Base {
    virtual ~Base() = default;
};

struct Bar;
struct Foo : Base {
    std::unique_ptr<Bar> bar_{};
};

https://godbolt.org/z/B0wvzd

我认为~Foo()必须隐式定义,因为它是虚拟的,但它不会编译,因为Bar在这个 TU 中不完整。然而,代码可以在所有主要编译器中编译。

我错过了什么?

4

1 回答 1

0

所以现在我想知道这段代码是否应该编译:

您是否“想知道”这个完整的程序是否“应该”编译

void f();
void g() { auto ff = &f; }
int main() {}

如果您假设它会编译,那么您认为代码会编译“多少次”?(它应该编译的是一个单一的真理,还是一个双重独立的真理?)

  1. f()以非使用方式“使用”(地址分配给优化后的局部变量)。
g():
        ret
  1. g()本身甚至没有被使用

任何“愚蠢”的链接器都可以看到,因为g()不需要,f()所以不需要;那是假设一个“愚蠢的”编译器会假设f()甚至需要g()

然而,一个未声明的函数 ( f()) 显然是 ODR 使用的。您是否期望这种实现行为,或者您是否“想知道”并提出有关它的问题?

如果您预计没有错误,您可能应该重新考虑整个“ODR 使用违规得到编译器通过让我想知道”的事情

实施不诊断缺乏定义,他们不需要制定工作程序。

他们需要什么取决于实现的聪明程度,很明显,如果不需要f()ing()隐藏在复杂的代码中,您可以使上述参数 1. 变得更加复杂。我特意举了一个简单的例子,一个在-O0.

[注意:这是g()级别的组装-O0

g():
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], OFFSET FLAT:_Z1fv
        nop
        pop     rbp
        ret

f()如您所见,不依赖于符号。]

知识取决于优化级别和代码生成时提供的数据量(同时编译许多翻译单元可能会有所帮助)。

对于非虚函数,该函数要么被所需事物(另一个函数或使用该函数地址初始化的全局对象)需要(即命名)。

对于虚函数,获取知识要困难得多:知道不会调用覆盖器并不像不是覆盖器(包括非虚函数)的函数那样简单,因为覆盖器可以通过调用“后期绑定”,当对(g)左值进行虚拟调用时。

编译器可能无法准确确定永远不需要哪些虚拟函数,具体取决于程序的复杂性(停止程序问题表明它们永远不会确切知道所有程序),但对象的非静态成员函数从未实例化显然永远不需要

于 2019-10-26T03:29:26.663 回答