C++单例模式
使用单例模式的理由
在开发过程中,很多时候一个类我们希望它只创建一个对象,比如:线程池、缓存、网络请求等。当这类对象有多个实例时,程序就可能会出现异常,比如:程序出现异常行为、得到的结果不一致等。
单例主要有这两个优点:
- 提供了对唯一实例的受控访问。
- 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
实现单例模式主要有以下几个关键点:
- 构造函数设置为 private ,这避免外部通过 new 创建实例;
- 通过一个静态方法或者枚举返回单例类对象;
- 考虑对象创建时的线程安全问题,确保单例类的对象有且仅有一个,尤其是在多线程环境下;
- 保单例类对象在反序列化时不会重新构建对象。
- 考虑是否支持延迟加载;
饿汉式
在类加载的期间,就已经将 instance 静态实例初始化好了,所以,instance 实例的创建是线程安全的。不过,这样的实现方式不支持延迟加载实例。
class Singleton1{
public:
static std::shared_ptr<Singleton1> getInstance(){
return _instance_ptr;
}
Singleton1(const Singleton1&)=delete;
Singleton1(Singleton1&&) = delete;
Singleton1& operator=(const Singleton1&) = delete;
Singleton1& operator=(Singleton1&&) = delete;
private:
Singleton1(){std::cout << "Singleton1()" << std::endl;} // 私有构造
~Singleton1(){std::cout << "~Singleton1()" << std::endl;}
static void destructInstance() {delete _instance_ptr;};
static std::shared_ptr<Singleton1> _instance_ptr;
};
std::shared_ptr<Singleton1> Singleton1::_instance_ptr = std::shared_ptr<Singleton1>(new Singleton1(), destructInstance);
懒汉式
懒汉式相对于饿汉式的优势是支持延迟加载。
class Singleton2{
public:
static std::shared_ptr<Singleton2> getInstance(){
if(!_instance_ptr)
_instance_ptr = std::shared_ptr<Singleton2>(
new Singleton2(), [](Singleton2* ptr){delete ptr;});
return _instance_ptr;
}
Singleton2(const Singleton2&)=delete;
Singleton2(Singleton2&&) = delete;
Singleton2& operator=(const Singleton2&) = delete;
Singleton2& operator=(Singleton2&&) = delete;
private:
Singleton2(){std::cout << "Singleton2()" << std::endl ;} // 私有构造
~Singleton2() {std::cout << "~Singleton2()" << std::endl;}
static std::shared_ptr<Singleton2> _instance_ptr;
};
该种方法并不线程安全,多个线程同时调用getInstance
并且同时进入if
分支,那么就会创建多个实例。
double-check加锁(饿汉式)
class Singleton3
{
public:
static std::shared_ptr<Singleton3> getInstance()
{
if(_instance == nullptr)
{
lock_guard<mutex> lock(_mtx);
if(!_instance)
_instance = std::shared_ptr<Singleton3>(
new Singleton3(), [](Singleton3* ptr){delete ptr;});
}
return _instance;
}
Singleton3(const Singleton3&) = delete;
Singleton3(Singleton3 &&) = delete;
Singleton3& operator=(const Singleton3&) = delete;
Singleton3& operator=(Singleton3 &&) = delete;
private:
Singleton3(){std::cout << "Singleton3()"<< std::endl;} // 私有构造
~Singleton3() {std::cout << "~Singleton3()"<< std::endl;}
static mutex _mtx;
static std::shared_ptr<Singleton3> _instance;
};
new
操作分三个阶段:
- 开辟空间;
- 构造对象;
- 返回指针。
在多cpu多线程中,由于编译器指令重排或者是cpu指令重排,可能使得按照1 -> 3 -> 2
的方式进行;返回指针后,其他线程不会通过第一个if
分支直接返回对象指针,由于此时对象还未构造完成,其他线程使用该指针将导致未定义行为。
使用静态局部变量(饿汉式)
在 C++11 中,静态局部变量的初始化是线程安全的。这意味着当多个线程并发访问一个函数时,如果该函数包含一个静态局部变量,只有第一个访问该变量的线程会执行初始化操作,而其他线程会等待初始化完成后再继续执行。C++11 的这一特性解决了多线程环境下静态局部变量可能导致的竞争条件问题。
静态局部变量在程序退出或作用域结束时会被自动销毁。C++ 会为静态局部变量调用析构函数,因此不需要手动管理其生命周期。在某些情况下,静态局部变量存储在 .data
段或 .bss
段中,这些变量会在程序终止时自动调用其析构函数。
class Singleton4{
public:
static Singleton4* getInstance(){
static Singleton4 instance{};
return &instance;
}
Singleton4(const Singleton4&) = delete;
Singleton4(Singleton4&&) = delete;
Singleton4& operator=(const Singleton4&) = delete;
Singleton4& operator=(Singleton4&&) = delete;
private:
Singleton4(){std::cout << "Singleton4()" << std::endl;} // 私有构造
~Singleton4() {std::cout << "~Singleton4()" << std::endl ;}
}
使用call_once
class Singleton5
{
public:
static Singleton5* getInstance()
{
std::call_once(_initFlag, [](){_instance = new Singleton5();});
return _instance;
}
Singleton5(const Singleton5&) = delete;
Singleton5(Singleton6 &&) = delete;
Singleton5& operator=(const Singleton5&) = delete;
Singleton5& operator=(Singleton5&&) = delete;
private:
Singleton5(){std::cout << "Singleton5()" << std::endl;} // 私有构造
~Singleton5() {std::cout << "~Singleton5()" << std::endl ;}
static Singleton5* _instance;
static std::once_flag _initFlag;
};
std::once_flag Singleton5::_initFlag;
标签:std,模式,instance,C++,单例,Singleton3,Singleton2,ptr,delete
From: https://www.cnblogs.com/sfbslover/p/18402717