调用方式如下
std::shared_ptr<SingleAuto> SingleAuto::single = nullptr;
std::mutex SingleAuto::s_mutex;
void test_singleauto()
{
auto sp1 = SingleAuto::GetInst();
auto sp2 = SingleAuto::GetInst();
std::cout << "sp1 is " << sp1 << std::endl;
std::cout << "sp2 is " << sp2 << std::endl;
//此时存在隐患,可以手动删除裸指针,造成崩溃
// delete sp1.get();
}
这样开辟的资源交给智能指针管理免去了回收资源的麻烦。
但是有些人觉得虽然智能指针能自动回收内存,如果有开发人员手动
delete
指针怎么办?
所以有人提出了利用辅助类帮助智能指针释放资源,将智能指针的析构设置为私有。
//为了规避用户手动释放内存,可以提供一个辅助类帮忙回收内存
//并将单例类的析构函数写为私有
class SingleAutoSafe;
class SafeDeletor
{
public:
void operator()(SingleAutoSafe* sf)
{
std::cout << "this is safe deleter operator()" << std::endl;
delete sf;
}
};
class SingleAutoSafe
{
private:
SingleAutoSafe() {}
~SingleAutoSafe()
{
std::cout << "this is single auto safe deletor" << std::endl;
}
SingleAutoSafe(const SingleAutoSafe&) = delete;
SingleAutoSafe& operator=(const SingleAutoSafe&) = delete;
//定义友元类,通过友元类调用该类析构函数
friend class SafeDeletor;
public:
static std::shared_ptr<SingleAutoSafe> GetInst()
{
//1处
if (single != nullptr)
{
return single;
}
s_mutex.lock();
//2处
if (single != nullptr)
{
s_mutex.unlock();
return single;
}
//额外指定删除器
//3 处
single = std::shared_ptr<SingleAutoSafe>(new SingleAutoSafe, SafeDeletor());
//也可以指定删除函数
// single = std::shared_ptr<SingleAutoSafe>(new SingleAutoSafe, SafeDelFunc);
s_mutex.unlock();
return single;
}
private:
static std::shared_ptr<SingleAutoSafe> single;
static std::mutex s_mutex;
};
SafeDeletor
就是删除的辅助类,实现了仿函数。构造智能指针时指定了
SafeDeletor
对象,这样就能帮助智能指针释放了。
但是上面的代码存在危险,比如懒汉式的使用方式,当多个线程调用单例时,有一个线程加锁进入3处的逻辑。
其他的线程有的在1处,判断指针非空则跳过初始化直接使用单例的内存会存在问题。
主要原因在于SingleAutoSafe * temp = new SingleAutoSafe() 这个操作是由三部分组成的
1 调用allocate开辟内存
2 调用construct执行SingleAutoSafe的构造函数
3 调用赋值操作将地址赋值给temp
而现实中2和3的步骤可能颠倒,所以有可能在一些编译器中通过优化是1,3,2的调用顺序,
其他线程取到的指针就是非空,还没来的及调用构造函数就交给外部使用造成不可预知错误。
为解决这个问题,C++11 推出了std::call_once函数保证多个线程只执行一次