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



考虑以下说明性示例

#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>::BaseA::A()的定义。

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


[1] As the consequence of [dcl.fct.def.default]/5:

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

的位置定义

所以C::C()有一个默认定义,它会立即生成"隐式";定义。

相关内容

  • 没有找到相关文章