【发布时间】:2018-10-08 10:19:15
【问题描述】:
我目前正在探索 Boost.Serialization 中与单例相关的一个非常棘手的错误。对于上下文:Boost 1.65 更改了打破is_destructed 通知的单例的实现,这会导致程序退出或库卸载时出现段错误。 Boost 1.66“修复”了这个问题,但会泄漏内存。
单例代码(与这个问题相关)归结为:
template<class T> struct singleton{
T& inst(){
static T t;
return t;
}
}
使用静态成员函数变量可以避免static init fiasco,但还是有同样的销毁问题。
然而Finding C++ static initialization order problems 显示了如何解决这个问题的代码:当A 的Ctor 使用B 时,B 将首先被构造,因此最后被破坏。这也在Destruction order of static objects in C++ 中说明。 (completion of the destructor happens in the reverse order of the completion of the constructor)
到目前为止一切顺利。 Boost.Serialization 现在使用多个extended_type_info_typeid<T> 类型的单例将用户类型T 的一些元数据注册到另一个单例std::multiset<const bs::typeid_system::extended_type_info_typeid_0*,...> 中。这是通过使用extended_type_info_typeid_0 的构造函数中的multiset(假设这里的所有单例)来完成的。在extended_type_info_typeid_0 的析构函数中,multiset 中的条目被删除。
这意味着我们完全符合上述情况,multiset 应该比其他实例寿命更长。
这在使用共享库时会出现问题。我有以下测试用例:
test_multi_singleton.cpp:
int f();
int g();
int main(int argc, char**){
// Make sure symbols are used
if(argc==8) return f();
if(argc==9) return g();
}
multi_singleton1.cpp:
#include <boost/serialization/extended_type_info_typeid.hpp>
int f(){
return 0 != boost::serialization::extended_type_info_typeid<int>::get_const_instance().get_key();
}
multi_singleton2.cpp:
#include <boost/serialization/extended_type_info_typeid.hpp>
int g(){
// Use different(!) type
return 0 != boost::serialization::extended_type_info_typeid<float>::get_const_instance().get_key();
}
Build with:
g++ multi_singleton1.cpp -lboost_serialization -fPIC -shared -olibmulti_singleton1.so
g++ multi_singleton2.cpp -lboost_serialization -fPIC -shared -olibmulti_singleton2.so
g++ test_multi_singleton.cpp -L. -lmulti_singleton1 -lmulti_singleton2
Run in valgrind:
valgrind ./a.out
可以看出这会破坏 Boost 1.65 中的内存。原因是我通过劫持和记录 ctor/dtor 调用来跟踪混乱的顺序:
ctor 0x7f9f0aa074a0 std::multiset<const extended_type_info_typeid_0*>
ctor 0x7f9f0a7f63e0 extended_type_info_typeid<float>
ctor 0x7f9f0a7f64a0 std::multiset<const extended_type_info_typeid_0*>
ctor 0x7f9f0aa073e0 extended_type_info_typeid<int>
dtor 0x7f9f0aa073e0 extended_type_info_typeid<int>
dtor 0x7f9f0aa074a0 std::multiset<const extended_type_info_typeid_0*>
dtor 0x7f9f0a7f64a0 std::multiset<const extended_type_info_typeid_0*>
dtor 0x7f9f0a7f63e0 extended_type_info_typeid<float>
这是使用 GCC 6.4,但与 GCC 7.1 相同。如您所见,2 个多重集在第 2 个 extended_type_info_typeid 之前被一起销毁。
我错过了什么吗?这是 C++ 标准允许的吗?
【问题讨论】:
-
解决这个问题(以及许多其他相关问题):不要使用单例。它们是一种反模式,往往会导致比它们解决的问题更多的问题。
-
标准没有说明动态加载库。
标签: c++ destructor static-variables