Singleton
单例模式(Singleton Pattern)是最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要显式实例化该类的对象。
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。
注意:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
优点
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
- 避免对资源的多重占用(比如写文件操作)。
缺点
没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景
1、要求生产唯一序列号。
2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
4、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
1. Lazy Singleton
这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁,所以严格意义上它并不算单例模式,适用于不要求线程安全的场景。
#ifndef LAZY_SINGLETON_H
#define LAZY_SINGLETON_H
class LazySingleton
{
private:
static LazySingleton *instance;
private:
LazySingleton();
~LazySingleton();
LazySingleton(const LazySingleton &);
LazySingleton &operator=(const LazySingleton &);
public:
static LazySingleton *getInstance();
};
#endif
#include "LazySingleton.h"
#include <iostream>
LazySingleton *LazySingleton::instance = nullptr;
LazySingleton *LazySingleton::getInstance()
{
if (instance == nullptr)
{
instance = new LazySingleton();
}
return instance;
}
LazySingleton::LazySingleton()
{
std::cout << "LazySingleton initialized!" << std::endl;
}
LazySingleton::~LazySingleton()
{
std::cout << "LazySingleton destroyed!" << std::endl;
}
上述Lazy Singleton的实现存在内存泄露的问题,有两种解决方法:
- 使用智能指针
- 使用静态的嵌套类对象
1.1 使用智能指针解决内存泄漏
#ifndef SHARED_SINGLETON_H
#define SHARED_SINGLETON_H
#include <memory>
class SharedSingleton
{
private:
static std::shared_ptr<SharedSingleton> instance;
SharedSingleton();
~SharedSingleton();
SharedSingleton(const SharedSingleton &);
SharedSingleton &operator=(const SharedSingleton &);
public:
static std::shared_ptr<SharedSingleton> getInstance();
};
#endif
#include "SharedSingleton.h"
#include <iostream>
#include <memory>
std::shared_ptr<SharedSingleton> SharedSingleton::instance = nullptr;
std::shared_ptr<SharedSingleton> SharedSingleton::getInstance()
{
if (instance == nullptr)
{
instance = std::shared_ptr<SharedSingleton>(
new SharedSingleton, [](SharedSingleton *obj){
delete obj;
});
}
return instance;
}
SharedSingleton::SharedSingleton()
{
std::cout << "SharedSingleton initialized!" << std::endl;
}
SharedSingleton::~SharedSingleton()
{
std::cout << "SharedSingleton destroyed!" << std::endl;
}
1.2 使用静态嵌套类对象解决内存泄漏
#ifndef INNER_SINGLETON_H
#define INNER_SINGLETON_H
class InnerDeletorSingleton
{
private:
static InnerDeletorSingleton *instance;
private:
InnerDeletorSingleton();
~InnerDeletorSingleton();
InnerDeletorSingleton(const InnerDeletorSingleton &);
InnerDeletorSingleton &operator=(const InnerDeletorSingleton &);
private:
class Deletor
{
public:
~Deletor()
{
if (InnerDeletorSingleton::instance != nullptr)
delete InnerDeletorSingleton::instance;
}
};
static Deletor deletor;
public:
static InnerDeletorSingleton *getInstance();
};
#endif
#include "InnerDeletorSingleton.h"
#include <iostream>
InnerDeletorSingleton::Deletor InnerDeletorSingleton::deletor;
InnerDeletorSingleton *InnerDeletorSingleton::instance = nullptr;
InnerDeletorSingleton *InnerDeletorSingleton::getInstance()
{
if (instance == nullptr)
{
instance = new InnerDeletorSingleton();
}
return instance;
}
InnerDeletorSingleton::InnerDeletorSingleton()
{
std::cout << "InnerDeletorSingleton initialized!" << std::endl;
}
InnerDeletorSingleton::~InnerDeletorSingleton()
{
std::cout << "InnerDeletorSingleton destroyed!" << std::endl;
}
1.3 线程安全的Singleton方案
Lazy Singleton模式的竞争条件主要出现在第一次初始化的过程中,instance = new Singleton()
处,即可能多个线程同时检测到instance未被初始化,于是开始执行初始化工作,为了避免重复初始化,需要对这一过程上锁。
#ifndef DCL_SINGLETON_H
#define DCL_SINGLETON_H
#include <thread>
class DCLSingleton
{
private:
static DCLSingleton *instance;
DCLSingleton();
~DCLSingleton();
DCLSingleton(const DCLSingleton &);
DCLSingleton &operator=(const DCLSingleton &);
static std::mutex mutex_;
public:
static DCLSingleton *getInstance();
};
#endif
#include "DCLSingleton.h"
#include <iostream>
DCLSingleton *DCLSingleton::instance = nullptr;
std::mutex DCLSingleton::mutex_;
DCLSingleton *DCLSingleton::getInstance()
{
if (instance == nullptr)
{
std::lock_guard<std::mutex> lk(mutex_);
if (instance == nullptr)
{
instance = new DCLSingleton();
}
}
return instance;
}
DCLSingleton::DCLSingleton()
{
std::cout << "DCLSingleton initialized!" << std::endl;
}
DCLSingleton::~DCLSingleton()
{
std::cout << "DCLSingleton destroyed!" << std::endl;
}
加入DCL后,其实还是有问题的,关于memory model。
在某些内存模型中(虽然不常见)或者是由于编译器的优化以及运行时优化等等原因,使得instance虽然已经不是nullptr但是其所指对象还没有完成构造,这种情况下,另一个线程如果调用getInstance()就有可能使用到一个不完全初始化的对象。
在C++11没有出来的时候,只能靠插入两个memory barrier(内存屏障)来解决这个错误,但是C++11引进了memory model,提供了Atomic实现内存的同步访问,即不同线程总是获取对象修改前或修改后的值,无法在对象修改期间获得该对象。
C++11后就可以正确的跨平台的实现DCL模式
std::atomic<DCLSingleton *>DCLSingleton::instance = nullptr;
2. Eager Singleton
#ifndef EAGER_SINGLETON_H
#define EAGER_SINGLETON_H
class EagerSingleton
{
private:
static EagerSingleton instance;
EagerSingleton();
~EagerSingleton();
EagerSingleton(const EagerSingleton &);
EagerSingleton &operator=(const EagerSingleton &);
public:
static EagerSingleton &getInstance();
};
#endif
#include <iostream>
#include "EagerSingleton.h"
EagerSingleton EagerSingleton::instance;
EagerSingleton &EagerSingleton::getInstance()
{
return instance;
}
EagerSingleton::EagerSingleton()
{
std::cout << "EagerSingleton initialized!" << std::endl;
}
EagerSingleton::~EagerSingleton()
{
std::cout << "EagerSingleton destroyed!" << std::endl;
}
由于在main函数之前初始化,所以没有线程安全的问题。
但是潜在问题在于no-local static对象(函数外的static对象)在不同编译单元中的初始化顺序是未定义的。
也即,static Singleton instance;和static Singleton& getInstance()二者的初始化顺序不确定,如果在初始化完成之前调用 getInstance() 方法
会返回一个未定义的实例。
3. Meyers Singleton
C++11规定了local static在多线程条件下的初始化行为,要求编译器保证了内部静态变量的线程安全性。
#ifndef MEYER_SINGLETON_H
#define MEYER_SINGLETON_H
class MeyerSingleton
{
private:
MeyerSingleton();
~MeyerSingleton();
MeyerSingleton(const MeyerSingleton &);
MeyerSingleton &operator=(const MeyerSingleton &);
public:
static MeyerSingleton &getInstance()
{
static MeyerSingleton instance;
return instance;
}
};
#endif
#include <iostream>
#include "MeyerSingleton.h"
MeyerSingleton::MeyerSingleton()
{
std::cout << "MeyerSingleton initialized!" << std::endl;
}
MeyerSingleton::~MeyerSingleton()
{
std::cout << "MeyerSingleton destroyed!" << std::endl;
}
4 总结
- Eager Singleton 虽然是线程安全的,但存在潜在问题;
- Lazy Singleton通常需要加锁来保证线程安全,但局部静态变量版本在C++11后是线程安全的;
- 局部静态变量版本(Meyers Singleton)最优雅。
5 注意事项
现有ABC三个库,其中A中封装了一个MeyerSingleton单例类,共享库B和共享库C使用A,D作为可执行程序,使用B和C,那么这个单例是否唯一?
以下是摘自chatgpt的回答:
如果A是一个共享库,封装了一个单例类,并且B和C都使用了A,那么D作为可执行程序,使用了B和C,这个单例类在整个程序中仍然是唯一的。单例类的唯一性是相对于进程而言的,因此由A封装的单例类在整个程序执行期间只会有一个实例,即使它被不同的共享库使用。
如果A作为静态库,封装了一个单例类,B和C使用A,D作为可执行程序使用B和C。由于静态库在链接时会被整合到可执行程序中,每个使用A的库和可执行程序中都将包含单例类的一个实例。因此,这个单例类在程序中仍然是唯一的,但是这个唯一性是相对于每个包含A的模块而言的,而不是整个程序。每个模块(B、C和D)都会有自己的单例实例。
此外,当libA作为静态库且在cpp文件中实现getInstance时,libB和libC同时作为动态库时出现单例不一致的问题。当libA作为动态库时,libB和libC作为动态库或者静态库时都没有这个问题 所以为了避免这个问题,最好的方式是,将getInstance的实现内联在.h文件中。
标签:LazySingleton,模式,InnerDeletorSingleton,instance,static,笔记,单例,DCLSingleton From: https://www.cnblogs.com/pengpengda/p/17991575