考虑以下说明性示例
#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() {
}
当上面的程序被GCC、Clang或vc++编译并执行时,人们会一致地看到以下输出:
Initialized!
所有三个编译器都发出Base<A>::touch
的定义和初始化,而没有发出Base<B>::touch
的定义和初始化(也通过godbolt验证)。所以我认为这是标准的制裁行为。
对于B
的默认构造函数,我们有
[class.ctor]
如果一个默认构造函数是默认的,但没有被定义为deleted,那么当它被用来创建类类型的对象([intro.object])时,或者当它在第一次声明之后被显式默认时,它被隐式定义。
由此可以得出结论,由于这两个条件在我们的TU中都不适用,因此B::B()
从来没有隐式定义。所以它从不使用Base<B>::Base()
和Base<B>::touch
。我觉得这很合理。
然而,我不明白为什么A::A()
最终使用其基类的成员。我们知道
[class.mfct]
1成员函数可以在其类定义中定义,在这种情况下,它是内联成员函数…
[dcl.inline]
6内联函数或变量应在每个使用odr的翻译单元中定义,并且在每种情况下都应具有完全相同的定义([basic.def.odr])。
[basic.def.odr]
4…类的构造函数是odr使用的,如[dcl.init]中指定的。
永远不会初始化任何A
类型的对象,因此不应该使用它的构造函数。因此,我们的程序最终不会包含任何A::A()
的定义。
那么为什么它表现得好像存在定义一样呢?为什么它不使用Base<A>::Base()
并导致它的实例化?
对于任何odr-使用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
B() = default
不使用Base<T>::Base
,因为它只被定义为默认值。即使在结构上是根据[basic.def]/2定义的,它也不是"发出的";因为上课。[qh]/7(如你所引述)。只有当B::B()
本身是odr-use时,才会(隐式)定义odr-useBase<T>::Base
的定义。
对于其他已定义的内联函数,则不存在这样的豁免。它们的定义无条件地(和结构化地)包含一个特殊的Base<T>::Base
。在你的例子中A::A()
是一个定义,你的程序确实包含了使用Base<T>::Base
的A::A()
的定义。
内联函数或变量应在其不被使用的每个翻译单元中定义。这是一个"应该"。需求(对于使用odr的内联函数的每个TU,都需要定义),而不是使用odr的内联函数的结果。
[1] As the consequence of [dcl.fct.def.default]/5:
用户提供的显式默认函数(即,在其第一次声明后显式默认)在显式默认
的位置定义
所以C::C()
有一个默认定义,它会立即生成"隐式";定义。