为什么内联用户提供的构造函数使用基类构造函数?

Sto*_*ica 5 c++ templates one-definition-rule language-lawyer

考虑以下说明性示例

#include <iostream>

template <typename>
struct Base {
    static int const touch;
    Base() {
        (void)touch;
    }
};

template<typename CRTP>
int const Base<CRTP>::touch = []{
    std::cout << "Initialized!\n";
    return 0;
}();

struct A : Base<A> {
    A() {} 
};

struct B : Base<B> {
    B() = default;
};

int main() {
}
Run Code Online (Sandbox Code Playgroud)

当上述程序由GCCClangVC++编译并执行时,我们始终会看到以下输出:

#include <iostream>

template <typename>
struct Base {
    static int const touch;
    Base() {
        (void)touch;
    }
};

template<typename CRTP>
int const Base<CRTP>::touch = []{
    std::cout << "Initialized!\n";
    return 0;
}();

struct A : Base<A> {
    A() {} 
};

struct B : Base<B> {
    B() = default;
};

int main() {
}
Run Code Online (Sandbox Code Playgroud)

所有三个编译器都会发出 的定义和初始化Base<A>::touch,而都不发出 的定义和初始化Base<B>::touch(也通过 godbolt 进行验证)。所以我得出的结论是这是标准的制裁行为。

对于 的默认构造函数B,我们有

[班级.ctor]

7当默认构造函数被默认且未定义为已删除时,当它被 odr 用于创建其类类型 ([intro.object]) 的对象时,或者当它在第一次声明后被显式默认时,它会被隐式定义。

由此可以得出结论,由于这两种条件都不适用于我们的 TU,B::B()因此从未隐式定义。所以它从不使用Base<B>::Base()Base<B>::touch。我觉得这很合理。

但是,我不明白为什么A::A()最终会使用其基类的成员。我们知道

[类.mfct]

1成员函数可以在其类定义中定义,在这种情况下,它是内联成员函数...

[dcl.内联]

6内联函数或变量应在使用 odr 的每个翻译单元中定义,并且在每种情况下都应具有完全相同的定义 ([basic.def.odr])。

[基本.def.odr]

4 ... 类的构造函数按 [dcl.init] 中指定的方式使用。

我们从不初始化任何类型的对象A,因此我们不应该使用它的构造函数。因此我们的程序最终不会包含 的任何定义A::A()

那么为什么它的表现就像存在定义一样呢?为什么它会被 ODR 使用Base<A>::Base()并导致其实例化?

Art*_*yer 4

对于odr-uses 的任何内联定义都会发生这种情况Base<T>::Base,例如:

inline void x() {
    Base<char>();
}

struct A : Base<A> {
    A(int) {}
    void x() {
        Base<int>();
    }
};

struct C : Base<C> {
    C();
};

inline C::C() = default;  // See [1]

// Will print "Initialized!" four times
Run Code Online (Sandbox Code Playgroud)

B() = default不使用 odr,Base<T>::Base因为它仅定义为defaulted。尽管在结构上是根据[basic.def]/2 的定义,但由于 [class.ctor]/7 (如您引用的),它不会“发出”。Base<T>::Base仅当 odr 使用本身时,才会(隐式)定义B::B()odr 使用的定义。

对于定义的其他内联函数,不存在这样的豁免。它们的定义无条件(并且在结构上)包含 odr-use Base<T>::BaseA::A()在您的示例中是一个定义,并且您的程序确实A::A()包含使用的定义Base<T>::Base

“内联函数或变量应在使用 odr 的每个翻译单元中定义”。这是一个“应”要求(对于每个使用 odr 函数的 TU,都需要一个定义),而不是 odr 使用内联函数的结果。


[1] 由于[dcl.fct.def.default]/5

用户提供的显式默认函数(即,在第一次声明后显式默认)在显式默认的位置定义

因此C::C()有一个默认定义,它立即生成“隐式”定义。