我们现在常常见到的单例模式的实现,也是来自 CppCoreGuidelines:
X& myX(){ static X my_x {3}; return my_x;}应该是最简单的一种实现了,简单且线程安全,在第一次执行时才会初始化对象。
然而,static 的析构顺序是未保证的,当多个单例相互具有依赖的时候,这种方式就无能为例了。
CppCoreGuidelines 里面也提到了
Note that the initialization of a local
staticdoes not imply a race condition. However, if the destruction ofXinvolves an operation that needs to be synchronized we must use a less simple solution.
可以使用:
X& myX(){ static auto p = new X {3}; return *p; // potential leak}如果不调用 delete,那么对象在程序的整个生命周期内,一直有效,就不会收到析构顺序的影响了。
Goole的代码中似乎常有这样的用法,例如 LevelDB 中的 NoDestructor :
namespace leveldb {
// Wraps an instance whose destructor is never called.//// This is intended for use with function-level static variables.template <typename InstanceType>class NoDestructor { public: template <typename... ConstructorArgTypes> explicit NoDestructor(ConstructorArgTypes&&... constructor_args) { static_assert(sizeof(instance_storage_) >= sizeof(InstanceType), "instance_storage_ is not large enough to hold the instance"); static_assert(std::is_standard_layout_v<NoDestructor<InstanceType>>); static_assert( offsetof(NoDestructor, instance_storage_) % alignof(InstanceType) == 0, "instance_storage_ does not meet the instance's alignment requirement"); static_assert( alignof(NoDestructor<InstanceType>) % alignof(InstanceType) == 0, "instance_storage_ does not meet the instance's alignment requirement"); new (instance_storage_) InstanceType(std::forward<ConstructorArgTypes>(constructor_args)...); }
~NoDestructor() = default;
NoDestructor(const NoDestructor&) = delete; NoDestructor& operator=(const NoDestructor&) = delete;
InstanceType* get() { return reinterpret_cast<InstanceType*>(&instance_storage_); }
private: alignas(InstanceType) char instance_storage_[sizeof(InstanceType)];};
} // namespace leveldb就是不调用析构函数,希望对象在程序的整个生命周期都有效。
前几天在微信公众号上看见一篇有关 union 的用法的帖子,原来 union 也能有类似的作用。
Absent default member initializers, if any non-static data member of a union has a non-trivial default constructor, copy constructor, move constructor, copy assignment operator, move assignment operator, or destructor, the corresponding member function of the union will be implicitly deleted unless it is user-provided. 在没有默认成员初始化器的情况下,如果 union 的任一非静态数据成员具有非平凡的默认构造函数、拷贝构造函数、移动构造函数、拷贝赋值运行符、移动赋值运算符或析构函数,则其相应的成员函数将被隐式删除(除非用户显式定义)。
使用的例子是:
namespace {
template<typename T>struct constant_init { union { T obj; }; constexpr constant_init() : obj() {}
~constant_init() { /* do nothing, union object is not destroyed */}};
struct some_class {};
constant_init<some_class> some_class_instantance {};
} // anonymous namespaceunion 里面根本不会调用 T 的析构函数。
又有方法,其实就类似上面 CppCoreGuidelines 里面说的第二种方法了:
struct T { /*...*/ };
T& instance() { static std::once_flag flag; alignas(T) static unsigned char buf[sizeof(T)]; static T* p = nullptr;
std::call_once(flag, []{ p = new (buf) T(); });
return *p;}使用 placement new 在一个局部的 buf 里面构造了一个对象,然后返回这个对象。只要我们不去调用析构函数就不会进行析构。