【问题标题】:Double initialization of a static STL container in a C++ libraryC++ 库中静态 STL 容器的双重初始化
【发布时间】:2023-04-03 16:41:01
【问题描述】:

这里有一些关于“静态初始化顺序惨败”的好问题和答案,但我似乎遇到了它的另一种表达方式,特别难看,因为它不会崩溃但会丢失和泄漏数据。

我有一个自定义 C++ 库和一个链接到它的应用程序。库中有一个静态 STL 容器,用于注册类的所有实例。这些实例恰好是应用程序中的静态变量。

由于“惨败”(我相信),我们在应用程序初始化期间让容器充满了应用程序实例,然后库开始初始化并且容器被重置(可能泄漏内存),最终只有库中的实例。

这就是我用简化代码重现它的方式:

mylib.hpp:

#include <iostream>
#include <string>
#include <vector>

using namespace std;

class MyLibClass {
    static vector<string> registry;
    string myname;
  public:
    MyLibClass(string name);
};

mylib.cpp:

#include "mylib.hpp"

vector<string> MyLibClass::registry;

MyLibClass::MyLibClass(string name)
: myname(name)
{
    registry.push_back(name);
    for(unsigned i=0; i<registry.size(); i++)
        cout << " ["<< i <<"]=" << registry[i];
    cout << endl;
}

MyLibClass l1("mylib1");
MyLibClass l2("mylib2");
MyLibClass l3("mylib3");

myapp.cpp:

#include "mylib.hpp"

MyLibClass a1("app1");
MyLibClass a2("app2");
MyLibClass a3("app3");

int main() {
    cout << "main():" << endl;
    MyLibClass m("main");
}

编译对象:

g++ -Wall -c myapp.cpp mylib.cpp
g++ myapp.o mylib.o -o myapp1
g++ mylib.o myapp.o -o myapp2

运行 myapp1:

$ ./myapp1
 [0]=mylib1
 [0]=mylib1 [1]=mylib2
 [0]=mylib1 [1]=mylib2 [2]=mylib3
 [0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1
 [0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1 [4]=app2
 [0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1 [4]=app2 [5]=app3
main():
 [0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=app1 [4]=app2 [5]=app3 [6]=main

运行 myapp2:

$ ./myapp2
 [0]=app1
 [0]=app1 [1]=app2
 [0]=app1 [1]=app2 [2]=app3
 [0]=mylib1
 [0]=mylib1 [1]=mylib2
 [0]=mylib1 [1]=mylib2 [2]=mylib3
main():
 [0]=mylib1 [1]=mylib2 [2]=mylib3 [3]=main

问题来了,静态向量是重新初始化的,还是在初始化之前使用的?这是预期的行为吗?

如果我将库“ar”为“mylib.a”(ar rcs mylib.a mylib.o),则问题不会发生,但可能是因为只有一个有效的命令可以链接到 .a 并且它是把图书馆放在最后,就像这里的 myapp1 一样。

但在我们的实际应用程序中,一个包含许多目标文件和几个静态 (.a) 库共享几个静态注册表的更复杂的应用程序中,问题正在发生,到目前为止,我们设法解决它的唯一方法是应用'[10.15] How do I prevent the "static initialization order fiasco"?'.

(我仍在研究我们有些复杂的构建系统,看看我们是否正确链接)。

【问题讨论】:

  • 顺便说一句非常好的示例代码。我希望所有问题都像这样。

标签: c++ static initialization static-libraries static-order-fiasco


【解决方案1】:

解决初始化顺序问题的一种方法是将静态变量从全局范围移动到本地范围。

不要在类中使用registry 变量,而是将其放入函数中:

vector<string> & MyLibClass::GetRegistry()
{
    static vector<string> registry;
    return registry;
}

在您应该直接使用registry 的地方,让它调用GetRegistry。

【讨论】:

  • 我选择这个答案是因为它提供了清晰的示例代码。经过研究,它似乎也是 Meyer 的,如上所述。
  • @gatopeich,是的,我在看到另一个答案后查了一下,但我从来不知道它有名字。
【解决方案2】:

如果你给vector&lt;string&gt; 一个自定义构造函数,你会看到,它确实只被调用了一次,但是在myapp2 你使用registry 首先未初始化,然后它被初始化(“删除”里面的所有东西)然后再次填充。它没有段错误只是运气:)

我不知道标准的哪一部分说明了这种行为,但恕我直言,你应该/永远/让静态变量相互依赖。例如,您可以将 Meyers 单例用于注册表。

【讨论】:

  • 确实,这就是我们选择的解决方案,但我不知道它是“Meyer's”。
  • 斯科特迈耶斯是名字。我认为他是第一个在他的“更有效的 C++”中提出这种单例实现的人。
【解决方案3】:

您正在使用 2 种已知技术。

(1) “模块/库/命名空间”作为“设备”模式

(2) 自定义类型注册,带有静态类。

用“Object Pascal”和“Plain C”做了类似的事情。我有几个文件,每个文件都作为模块/命名空间工作,带有 typedef、类、函数。 此外,每个“命名空间”都有 2 个特殊方法(相同的签名或原型),模拟连接设备和断开设备。已经尝试自动调用这些方法,但是执行顺序也出错了。

静态、单例类可能会变得一团糟。我建议,忘记使用宏或预处理器/编译器,自己调用初始化/终结方法。

----
mylib.hpp
----

class MyLibClass {
  public:
    Register(string libraryName);
    UnRegister(string libraryName);
};

// don't execute the "custom type registration here"

-----
mynamespace01.cpp
-----
#include "mylib.hpp"

void mynamespace01_otherstuff() { ... }

// don't execute registration
void mynamespace01_start() { 
  if not (MyLibClass::IsUnRegistered("mynamespace01")) MyLibClass::Register("mynamespace01");
}

void mynamespace01_finish()
{ 
  if not (MyLibClass::IsRegistered("mynamespace01")) MyLibClass::UnRegister("mynamespace01");
}

-----
mynamespace02.cpp
-----
#include "mylib.hpp"

// check, "2" uses "1" !!!
#include "mynamespace01.hpp"

void mynamespace02_otherstuff() { ... }

// don't execute registration !!!
void mynamespace02_start() { 
  // check, "2" uses "1" !!!
  void mynamespace01_start();

  if not (MyLibClass::IsUnRegistered("mynamespace01")) MyLibClass::Register("mynamespace02");

  void mynamespace02_start();  
}

void mynamespace02_finish(){ 
  void mynamespace02_finish();

  if not (MyLibClass::IsRegistered("mynamespace02")) MyLibClass::UnRegister("mynamespace02");

  // check, "2" uses "1" !!!
  void mynamespace02_start();  
}

-----
myprogram.cpp
-----

#include "mynamespace01.hpp"
#include "mynamespace02.hpp"

void myprogram_otherstuff() { ... }

// don't execute registration !!!
void myprogram_start() { 
  // check, "2" uses "1" !!!
  mynamespace01_start();
  mynamespace02_start();

  if not (MyLibClass::IsUnRegistered("myprogram")) MyLibClass::Register("myprogram");
}
void myprogram_finish() {
  if not (MyLibClass::IsRegistered("myprogram")) MyLibClass::UnRegister("myprogram");

  // check, "2" uses "1" !!!  
  mynamespace01_finish();
  mynamespace02_finish();  
}

void main () {
  // all registration goes here !!!:

  // "device" initializers order coded by hand:
  myprogram_start();

  // other code;

  // "device" finalizers order inverse coded by hand:  
  myprogram_finish();
}
-----

检查此代码是否比您的更复杂和冗长, 但是,根据我的经验,它更稳定。

我还将“finalizer”添加到“initializer”,并将标识符替换为“Register”。

祝你好运。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-05-04
    • 1970-01-01
    • 2023-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-26
    • 2011-05-22
    相关资源
    最近更新 更多