【问题标题】:What are rules for a class static variable initialization?类静态变量初始化的规则是什么?
【发布时间】:2017-05-04 12:01:25
【问题描述】:

我有一些遗留代码,我需要为消息添加一个新类(这与我的问题无关)。但事实证明,我需要声明一个空的构造函数才能初始化一些静态。不是默认构造函数或编译器提供的,而是用户定义的空。我试图将代码简化为 MWE,我得到的结果如下:

#include <iostream>

using namespace std;

struct Test
{
    Test() {cout << "Test::Test()" << "\n";}
    void dummy(){}
};

template<typename T>
struct Message
{
    Message()
    {
        test.dummy(); // this call have to be here in order to initialize Test, but why?
    }

    static Test test;
};

template<typename T>
Test Message<T>::test;

struct A : public Message<A>
{
    //A(){} // uncomment this (and comment the default one) to call the Test constructor
    A() = default;
};

int main()
{
}

这就是发生的事情:

  • 程序本身是空的,即没有创建实例。
  • A 类有一个 CRTP,这似乎对示例很重要。
  • A 的基础有一个静态声明,我期待它的构造函数被调用。
  • 有一个对函数的虚拟调用,它什么都不做,但也很关键。

问题是,如果我不提供自定义构造函数,则永远不会调用静态构造函数。我不明白为什么我需要这个?默认或编译器生成有什么区别?为什么我需要调用一个虚拟函数?

我相信这是有规律的。我用不同版本的 gcc 和 clang 检查了它——行为是一样的。我非常感谢标准/文档的链接。

【问题讨论】:

  • 您能否更清楚地解释在运行您发布的代码时观察到的行为(即发布确切的输出),并解释它与您的预期有何不同?
  • 如果我为A 提供一个空的构造函数,我会看到Test 构造函数的输出。如果我省略构造函数或声明它default 我什么也观察不到。您可以通过评论/取消评论呼叫来检查它here

标签: c++ static crtp


【解决方案1】:

如果您将A 构造函数设为默认并且从不调用它,则无需生成它,因此无需创建test。如果在定义时显式默认,调用A构造函数或访问A::test,它将被正确初始化。

12.1 构造函数 [class.ctor]

7 一个默认且未定义为已删除的默认构造函数在被 odr 使用 (3.2) 创建其类类型 (1.8) 的对象时或在其第一次声明后显式默认时被隐式定义。

struct A : public Message<A>
{
    A() = default; // no constructor is emitted unless A is instantiated

    A(); // declaration
};

A::A() = default; // explicit default definition

int
main()
{
    A a; // instantiation
    A::test; // just explicitly access test so it is initialized regardless of A constructor
}

【讨论】:

  • 嘿,我已经完成了另一半的谜题,但还没有。..:)
  • 这就是答案。我确信构造函数总是存在的——编译器至少会生成一个构造函数。使用compiler explorer,我可以看到构造函数汇编器仅为显式默认定义而生成。谢谢。
【解决方案2】:

C++14 [temp.inst]/2:

除非类模板或成员模板的成员已被显式实例化或显式特化,否则当在需要成员定义存在的上下文中引用特化时,会隐式实例化该成员的特化;特别是,静态数据成员的初始化(以及任何相关的副作用)不会发生,除非该静态数据成员本身的使用方式要求该静态数据成员的定义存在。

这清楚地表明Message&lt;A&gt;::test 不会被初始化,除非它以需要其定义存在的方式使用。

您的程序中唯一需要定义的表达式是 Message&lt;A&gt; 的构造函数中的 test.dummy() ;因此,如果删除该表达式,则不得初始化 test

对于存在test.dummy() 的情况,请注意它位于模板函数内部,即Message&lt;A&gt; 的构造函数。如果从未实例化此构造函数,则不会考虑 test.dummy()

正如 VTT 所指出的,[class.ctor] 表示 A 的显式默认构造函数意味着除非使用了 A,否则不会定义构造函数。

您的代码没有使用 A,因此未定义 A 的构造函数,因此没有调用基类构造函数(只有当 A 的构造函数被定义时才会发生这种情况定义),因此构造函数模板Message&lt;A&gt;()没有被实例化,因此test不需要存在。

【讨论】:

  • 令人惊讶的拼图如何组合在一起。感谢您的详尽解释。
猜你喜欢
  • 1970-01-01
  • 2012-02-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-10
  • 2011-08-22
  • 2010-12-22
  • 2018-12-19
相关资源
最近更新 更多