转载自:Zhongtian‘s Technical Notes,如侵联删。
单例模式是使用最广泛的设计模式之一,其目的是保证一个类仅有一个实例,并提供一个访问它的全局访问点。
1 Eager Singleton
class Singleton {
public:
static Singleton& GetInstance() {
return instance;
}
private:
Singleton() {};
~Singleton() {};
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
static Singleton instance;
};
由于在 main 函数之前初始化,所以该实现方式没有线程安全的问题。但是潜在问题在于 no-local static 对象(函数外的 static 对象)在不同编译单元中的初始化顺序是未定义的。也即 static Singleton instance;
和 static Singleton& GetInstance()
二者的初始化顺序不确定,如果在初始化完成之前调用 GetInstance()
方法会返回一个未定义的实例。
2 Lazy Singleton
2.1 线程不安全
class Singleton {
public:
static Singleton* GetInstance() {
if (instance == nullptr)
instance = new Singleton();
return instance;
}
private:
Singleton() {}
~Singleton() {}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
static Singleton* instance;
};
注意:
- C++ 规定 const 静态类成员可以直接初始化,其他非 const 的静态类成员需要在类声明以外初始化,我们一般选择在类的实现文件中初始化。
- 静态成员在 cpp 文件中也声明一下,否则编译时会提示 undefined reference。
2.2 线程安全,存在 memory order 潜在问题:双检测锁模式(Double-Checked Locking Pattern, DCLP)
class Singleton {
public:
static Singleton* GetInstance() {
if (instance == nullptr) {
std::lock_guard<std::mutex> mutx(some_mutex);
if (instance == nullptr) {
instance = new Singleton();
}
}
return instance;
}
private:
Singleton() {}
~Singleton() {}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
static Singleton* instance;
std::mutex some_mutex;
};
如果第一次检测不做则会每次获取都加锁,在实例已经创建的情况下这是没有必要的。在第一次检测实例是否被创建和加锁的操作之间,可能有另一个线程创建了实例,所以第二次检测也是必不可少的。
2.3 线程安全:atomic
DCLP 其实也存在问题。在某些内存模型中或者是由于编译器的优化以及运行时优化等等原因,使得 instance 虽然已经不是 nullptr 但是其所指对象还没有完成构造,这种情况下,另一个线程如果调用 GetInstance() 就有可能使用到一个不完全初始化的对象。在 C++11 没有出来的时候,只能靠插入两个 memory barrier(内存屏障)来解决这个错误,但是 C++11 引进了 memory model,提供了 atomic 实现内存的同步访问,即不同线程总是获取对象修改前或修改后的值,无法在对象修改期间获得该对象。
class Singleton {
public:
static Singleton* GetInstance() {
Singleton* tmp = instance;
if (tmp == nullptr) {
std::lock_guard<std::mutex> mutx(some_mutex);
tmp = instance; // tmp = instance.load(memory_order_seq_cst);
if (instance == nullptr) {
tmp = new Singleton();
instance = tmp; // instance.store(tmp, memory_order_seq_cst);
}
}
return instance;
}
private:
Singleton() {}
~Singleton() {}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
static std::atomic<Singleton*> instance;
std::mutex some_mutex;
};
3 最佳实践,Meyers’ Singleton
3.1 实践方式
class Singleton {
public:
static Singleton& GetInstance() {
static Singleton inst;
return inst;
}
};
单例模式的两个特性以如下方式被保证:
- 线程安全性:C++11 规定了局部静态变量在多线程条件下的初始化行为,要求编译器保证了局部静态变量的线程安全性
- 单次初始化:C++11 规定了局部静态变量在代码第一次执行到变量声明的地方时初始化,局部静态变量的特性保证了其唯一性
另外,C++11 还提供了 std::call_once 函数,也可以用来实现多线程安全的单例对象初始化。
3.2 可能存在的问题
在这样一种情况下:
- 有一个动态库和一个调用该动态库的主程序
- Meyers Singleton 类定义在动态库中
- 在动态库的 .cpp 文件和主程序中分别调用 GetInstance() 方法
这两次调用的单例其实并不是同一个对象,造成这种现象的原因是每个动态库都有自己的静态数据实例。
解决方法主要有:
- 避免在库中调用单例
- 使用 thread locale storage
- 构建方式换成静态库
4 使用std::call_once构建单例
线程安全版本
#include <<mutex>>
template <class T> class ThreadSafeSingleton {
public:
static T& get() {
std::call_once(ThreadSafeSingleton<T>::create_once_, &ThreadSafeSingleton<T>::Create);
return *ThreadSafeSingleton<T>::instance_;
}
protected:
static void Create() { instance_ = new T(); }
static std::once_flag create_once_;
static T* instance_;
};
template <class T> std::once_flag ThreadSafeSingleton<T>::create_once_;
template <class T> T* ThreadSafeSingleton<T>::instance_ = nullptr;
支持多参数版本的单例类
#include <iostream>
#include <mutex>
#include <cassert>
template<class T>
class ThreadSafeSingleton {
public:
template<typename...Args>
static T& Getinstance(Args&&... args) {
std::call_once(ThreadSafeSingleton<T>::_call_once_flag,
std::forward<void(Args&&...)>(&ThreadSafeSingleton<T>::init),
std::forward<Args>(args)...);
return *ThreadSafeSingleton<T>::m_instance;
}
private:
ThreadSafeSingleton() = default;
~ThreadSafeSingleton() {
delete ThreadSafeSingleton<T>::m_instance;
ThreadSafeSingleton<T>::m_instance = nullptr;
}
ThreadSafeSingleton(const ThreadSafeSingleton& o) = delete;
ThreadSafeSingleton& operator=(const ThreadSafeSingleton& o) = delete;
template<typename...Args>
static void init(Args&&...args) {
m_instance = new T(std::forward<Args>(args)...);
}
private:
static std::once_flag _call_once_flag;
static T* m_instance;
};
template<class T> T* ThreadSafeSingleton<T>::m_instance = nullptr;
template<class T> std::once_flag ThreadSafeSingleton<T>::_call_once_flag;
测试调用代码
class TestSingleton1 {
public:
TestSingleton1(const std::string&){ std::cout << "lvalue" << std::endl;}
TestSingleton1(std::string&&){ std::cout << "rvalue" << std::endl;}
~TestSingleton1() = default;
void testFunc() {
std::cout << "test function 1" << "\n";
}
};
class TestSingleton2 {
public:
TestSingleton2(const std::string&){ std::cout << "lvalue" << std::endl;}
TestSingleton2(std::string&&){ std::cout << "rvalue" << std::endl;}
~TestSingleton2() = default;
void testFunc() {
std::cout << "test function 2" << "\n";
}
};
class TestSingleton3 {
public:
TestSingleton3(const std::string&,int i,double k){ std::cout << "lvalue" << std::endl;}
~TestSingleton3() = default;
void testFunc() {
std::cout << "test function 3" << "\n";
}
};
int main(int argc, char **argv) {
std::string str = "bb";
ThreadSafeSingleton<TestSingleton1>::Getinstance(str).testFunc();
ThreadSafeSingleton<TestSingleton2>::Getinstance(std::move(std::string("xxxx"))).testFunc();
ThreadSafeSingleton<TestSingleton3>::Getinstance("yyyy",1,2.0).testFunc();
return 0
}
参考
本文作者:StubbornHuang
版权声明:本文为站长原创文章,如果转载请注明原文链接!
原文标题:C++ – 单例模式
原文链接:https://www.stubbornhuang.com/2767/
发布于:2023年08月25日 14:39:29
修改于:2024年01月08日 17:17:57
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
评论
52