【问题标题】:Why does an inline user-provided constructor odr-uses base class constructors?为什么内联用户提供的构造函数 odr-使用基类构造函数?
【发布时间】:2021-01-23 16:32:03
【问题描述】:

考虑以下说明性示例

#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() {
}

当上述程序被GCCClangVC++编译并执行时,始终看到如下输出:

Initialized!

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

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

[class.ctor]

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

从中可以得出结论,由于这两个条件都不适用于我们的 TU,因此永远不会隐式定义 B::B()。所以它永远不会使用 Base&lt;B&gt;::Base()Base&lt;B&gt;::touch。我觉得这很合理。

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

[class.mfct]

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

[dcl.inline]

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

[basic.def.odr]

4 ... 类的构造函数按照 [dcl.init] 中的规定进行 odr-used。

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

那么为什么它表现得好像存在定义一样?为什么它使用 Base&lt;A&gt;::Base() 并导致其实例化?

【问题讨论】:

    标签: c++ templates language-lawyer one-definition-rule


    【解决方案1】:

    这发生在 任何 odr 使用 Base&lt;T&gt;::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&lt;T&gt;::Base,因为它仅定义为 defaulted。尽管在结构上是根据[basic.def]/2 定义的,但由于 [class.ctor]/7 (如您所引用的),它不会“发出”。只有当B::B() 本身是odr-used 时,才会(隐式)定义将odr-use Base&lt;T&gt;::Base 的定义。

    对于已定义的其他内联函数,不存在此类豁免。它们的定义无条件(和结构上)包含一个 odr-use Base&lt;T&gt;::BaseA::A() 在您的示例中是一个定义,而您的程序确实包含使用 Base&lt;T&gt;::BaseA::A() 的定义。

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


    [1] 作为[dcl.fct.def.default]/5的结果:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-01-28
      • 1970-01-01
      • 2016-07-19
      • 2018-07-21
      • 1970-01-01
      • 1970-01-01
      • 2011-02-24
      • 2018-07-16
      相关资源
      最近更新 更多