【问题标题】:How are static members of templated class constructed when a static function is called?调用静态函数时如何构造模板类的静态成员?
【发布时间】:2021-06-03 15:15:07
【问题描述】:

我试图更好地了解模板类/结构中静态成员的初始化。

让我们看下面的最小示例(在Coliru 中可用):

#include <iostream>

struct A {
  int value;
  A(int val) : value{val} { std::cout << "A(" << value << ")\n"; }
};

struct Static {
  static inline A s_a{42};
  
  static void use() {
    std::cout << s_a.value++ << '\n';
    std::cout << s_a.value << '\n';
  }
};

struct User {
   User() {
    Static::use();
  }
};

User s_user{};

int main() {
  return 0;
}

输出是(如我所料):

A(42)
42
43

现在,我希望能够为不同类型提供多个版本的Static,因此我将其转换为模板(Coliru):

#include <iostream>

struct A {
  int value;
  A(int val) : value{val} { std::cout << "A(" << value << ")\n"; }
};

template<class T>
struct Static {
  static inline A s_a{42};
  
  static void use() {
    std::cout << s_a.value++ << '\n';
    std::cout << s_a.value << '\n';
  }
};

struct User {
   User() {
    Static<int>::use();
  }
};

User s_user{};

int main() {
  return 0;
}

然后输出是

0
1
A(42)

如您所见,A::value 未初始化使用,A 的构造函数在Static&lt;int&gt;::use 执行后调用

我可以更改s_a 的定义,使其专门用于高级:

template<class T>
struct Static {
  static A s_a;
  // ...
};

template<> A Static<int>::s_a{42};

它可以正常工作 (Coliru)。对应的是我必须定义每一个专业化,我想让它尽可能容易使用(你可以假设实际代码更复杂)。

所以,我的问题是:

  1. 为什么Static&lt;T&gt;::use() 在它(s_a)被初始化之前使用s_a?我可以考虑某种延迟初始化直到专业化(对不起,不知道它的正确名称),并且User&lt; 正在调用一个静态函数,不知何故,与静态属性(只是它们在同一个封装中),但我不知道确切的原因,也不知道标准对此有何规定。

  2. 我应该如何更改此代码以获得与非模板版本相同的行为?也就是说,任何Static&lt;T&gt; 的属性都可以在调用其静态方法之前进行初始化。

注意:struct A 只是这里的一个最小示例,在我正在处理的项目中,它是一个基于 Static&lt;T&gt; 的模板化类。

【问题讨论】:

    标签: c++ templates static initialization c++17


    【解决方案1】:

    C++ 中的静态初始化是一场噩梦...... C++17 的 n4659 草案在 6.6.3 非局部变量的动态初始化[basic.start.dynamic] §1 中说

    如果变量是隐式或显式实例化的特化,则具有静态存储持续时间的非局部变量的动态初始化是无序的,如果变量是不是隐式或显式实例化的特化的内联变量,则它是部分排序的,并且否则订购。

    Static 类没有被模板化时,你有一个部分有序的初始化来保证Static::s_a 的初始化发生在s_user 的初始化之前。但是当你使用模板时,初始化变得无序,实现可以选择它想要的。在我的测试中,gcc 10 给出的结果与你得到的结果相同(s_usersStatic::s_a 之前初始化),但 clang 12 给出了相反的顺序......

    您很幸运使用了 gcc 并看到了问题。如果您使用 Clang,您将在开发时继续,问题可能会在以后发生。更糟糕的是,我不确定显式实例化是否真的保证了初始化顺序。如果我正确理解了标准,我会说不,无论如何,我永远不会依赖它来生产代码。

    【讨论】:

    • 谢谢!所以,结论不是靠这个。那我能做什么?我能想到的只是一个单例模式,它虽然不是静态链接的,但会提供静态版本的 global-like 可见性,并且由于成员不再是静态的,我们可以确定它是使用之前正确初始化。对?我做了一个测试here 和 VS,两者都正常工作,但我想知道你的想法。谢谢!
    • @cbuchart:我能给你的最好的参考来自另一个SO question。 C++ 讨厌静态初始化,推荐的模式是“第一次使用的构造”。如果您在单例模式上使用它,那么您将是安全的......
    • 谢谢谢尔盖!是的,单例模式基本上就是这样做的:确保在首次使用之前创建静态对象。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-21
    • 1970-01-01
    • 2017-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多