如何处理 Boost Spirit X3 在 Visual Studio 2019 中造成的 "static initialization order fiasco"?

How to deal with Boost Spirit X3 causing a "static initialization order fiasco" in Visual Studio 2019?

我正在 boost::spirit::x3 之上用 C++ 开发一个重要的解析器。我将我的解析代码拆分成逻辑单元,其中一些是相互依赖的。例如,一个单元是一个表达式解析器,它还公开一个标识符解析器。目标语言的许多高级句法结构包括表达式和标识符,因此该单元通常是一个依赖项。我已将代码拆分为三个文件 as the documentation recommends。如果有单位 foobarquux,我有这样的文件:

parser
    foo.h
    foo_def.h
    foo.cpp
    bar.h
    bar_def.h
    bar.cpp
    quux.h
    quux_def.h
    quux.cpp

其中 .h 扩展 BOOST_SPIRIT_DECLARE_def.h 扩展了 BOOST_SPIRIT_DEFINE.cpp 扩展了 BOOST_SPIRIT_INSTANTIATE。我 运行 遇到了一个持续存在的问题,由于 Spirit 代码中的静态初始化失败(.../x3/nonterminal/rule.hpp 的第 160 行),我的代码在启动时出错,但仅当 运行调试版本。

基于像 and 这样的 Whosebug 问题和答案,加上在发布配置中构建时代码执行无误的事实,我相信我的代码没有任何问题。如果无法在调试中修复我的代码,我可以看到两种解决方法:

  1. 仅在调试版本中使用 pragma 声明来更改静态初始化的顺序。 (更改 .vcxproj 文件中项目的顺序不会影响初始化顺序)在 Visual Studio 中,如果我在表达式解析器的 .cpp 文件中放入 #pragma init_seg(lib),问题就会消失。

  2. 仅在调试版本中包含必要的多余 _def.h 文件,例如如果 quux 依赖于上面的 bar 那么在 quux.cpp 中包含 bar_def.h 将解决问题,但会破坏拆分的目的解析器分成多个文件。

我想知道这个问题的总体情况是什么?这似乎是一个已知问题,但已经存在很长时间了,所以我不应该指望它会在精神层面得到修复?有没有更好的方法来构建我的代码,这样这个问题就不会发生?我想知道是否有可能,例如,在首次使用时创建的函数范围内将所有解析器及其子解析器创建为静态变量,但文献中没有这样的例子,所以我不确定这是否可能?

通常的方法是让函数通过引用返回局部静态。

Function-local 静力学

  • 具有静态 "lifetime"(存储持续时间)但只会在首次使用时初始化(避免惨败)
  • 从 C++11 开始,您甚至可以依赖初始化 thread-safe

因此,您可能在 header:

my_rule_type const& my_rule();

并且在定义规则的 cpp 中:

my_rule_type const& my_rule() {
     static const my_rule_type s_my_rule = ns::my_rule;
     return s_my_rule;
}

更新

实际上 documentation example 不会为返回引用而烦恼,而是每次都返回一个副本:

parser::employee_type employee()
{
    return parser::employee;
}

这表明这样做不会有很大的开销,我建议您避免那里 reference-semantics 的复杂化。