考虑以下说明性示例
#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 https://wandbox.org/permlink/7l6RScgFkvbPeCZY, Clang https://wandbox.org/permlink/n0c7Nj7N2sRMPTq6 or VC++ https://rextester.com/VIFKJ50959并执行后,人们始终会看到以下输出:
Initialized!
所有三个编译器都会发出以下定义和初始化Base<A>::touch
,而两者都没有发出定义和初始化Base<B>::touch
(也通过 godbolt 验证)。所以我得出的结论是这是标准的制裁行为。
对于默认构造函数B
, 我们有
[班级.ctor]
7 https://timsong-cpp.github.io/cppwp/n4659/class.ctor#7当默认构造函数被默认且未定义为已删除时,当它被 odr 用于创建其类类型 ([intro.object]) 的对象时,或者当它在第一次声明后被显式默认时,它会被隐式定义。
由此可以得出结论,由于这两种条件都不适用于我们的 TU,B::B()
从来没有隐式定义。所以它永远不会使用Base<B>::Base()
and Base<B>::touch
。我觉得这很合理。
但是,我不明白为什么A::A()
最终使用其基类的成员进行 ODR。我们知道
[类.mfct]
1 https://timsong-cpp.github.io/cppwp/n4659/class.mfct#1成员函数可以在其类定义中定义,在这种情况下,它是内联成员函数...
[dcl.内联]
6 https://timsong-cpp.github.io/cppwp/n4659/dcl.inline#6内联函数或变量应在使用 odr 的每个翻译单元中定义,并且在每种情况下都应具有完全相同的定义 ([basic.def.odr])。
[基本.def.odr]
4 https://timsong-cpp.github.io/cppwp/n4659/basic.def.odr#6...类的构造函数按照 [dcl.init] 中指定的方式使用。
我们从不初始化任何类型的对象A
,所以我们不应该使用它的构造函数。所以我们的程序最终不会包含任何定义A::A()
.
那么为什么它的表现就像存在定义一样呢?为什么要使用odrBase<A>::Base()
并导致其实例化?