我们现在常常见到的单例模式的实现,也是来自 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 namespace
union 里面根本不会调用 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 里面构造了一个对象,然后返回这个对象。只要我们不去调用析构函数就不会进行析构。