(1)设计模式解决什么问题?本质上是分析稳定点和变化点。在开发过程中要抽象稳定的,扩展变化点。设计模式具体解决问题的场景:希望修改少量的代码,就可以适应需求的变化。 (2)设计模式的代码结构是什么?代码结构反映出使用了什么设计模式。 (3)设计模式符合哪些设计原则?因为设计模式是由设计原则推导来的,可以按照设计模式产生的流程重新思考,能够很好的帮助我们去设计代码。 (4)如何在上面扩展代码? (5)该设计模式有哪些典型应用场景。
class T{
public:
T(){
cout<<"T():"<<this<<endl;
}
~T(){
cout<<"~T():"<<this<<endl;
}
T(const T&){
cout<<"T(const T&) 拷贝构造:"<<this<<endl;
}
T& operator=(const T&){
cout<<"T& operator=(const T&)拷贝赋值构造: "<<this<<endl;
}
T(T &&){
cout<<"T(T &&)移动构造: "<<this<<endl;
}
T& operator=(T &&){
cout<<"T& operator=(T &&)移动赋值构造: "<<this<<endl;
}
};
T CreateT(){
T temp;
return temp;
}
拷贝构造的触发有三个方式: (1)直接用对象构造
T t1;
T t2=t1;
(2)传入参数构造
T t1;
T t2(t1);
(3)C++ 11出现的 初始化列表的构造方式
T t1;
T t2{t1};
两个对象之间赋值。
T t1;
T t2;
t1=t2;
C++11出现的,有两种方式: (1)函数l返回。
T t=CreateT();
注意,如果没有禁掉返回值优化:-fno-elide-constructors;就会只有一个构造函数和一个析构函数,只进行一次构造和一次析构。 如果禁掉返回值优化,编译器有三种行为:看类有没有移动构造;如果没有移动构造就看类有没有拷贝构造;如果两个都没有就会报错。
(2)std::move()
T t1;
T t2(std::move(t1));
方式一:
T t;
t=T();
方式二:
T t1,t2;
t1=std::move(t2);
保证一个类仅有一个实例,并提供一个该实例的全局访问点。
(1)稳定点:类只有一个实例,提供一个全局的访问点。抽象稳定点。 (2)变化点:有多个类都是单例,能不能复用代码。扩展变化点,继承、组合的方式。
(1)私有的构造和析构。单例模式和程序的生命周期是相同的,不希望new和delete的存在,应用程序退出时单例模式才会释放。所以,需要把构造函数和析构函数隐藏起来,让用户不能调用。 (2)禁掉一些构造。把所有能构造的方式都关闭。比如 拷贝构造、拷贝赋值构造、移动构造、移动拷贝构造。 (3)静态类成员函数。 (4)静态私有成员变量。
class Singleton {
public:
static Singleton * GetInstance() {
if (_instance == nullptr) {
_instance = new Singleton();
}
return _instance;
}
private:
Singleton(){}; //构造
~Singleton(){};
// = delete 就是关闭这些行为
Singleton(const Singleton &) = delete; //拷⻉构造
Singleton& operator=(const Singleton&) =delete;//拷贝赋值构造
Singleton(Singleton &&) = delete;//移动构造
Singleton& operator=(Singleton &&) =delete;//移动拷贝构造
static Singleton * _instance;
};
Singleton* Singleton::_instance = nullptr;//静态成员需要初始化
_instance是静态全局分配的,程序退出自动释放。但是,_instance是一个指针,指向一个堆,_instance虽然释放了,但堆不自动释放。如果这个类是对文件操作的,那么程序退出时就无法关闭文件句柄和将未来得及写入文件的内容写入文件中。 当然,将_instance改用智能指针unique_ptr可以解决这个问题。
为解决上述的问题,增加一个接口。
class Singleton {
public:
static Singleton * GetInstance() {
if (_instance == nullptr) {
_instance = new Singleton();
atexit(Destructor);// 当程序退出时调用atexit里设置的Destructor函数
}
return _instance;
}
private:
static void Destructor() {
if (nullptr != _instance) { //
delete _instance;
_instance = nullptr;
}
}
Singleton(){}; //构造
~Singleton(){};
Singleton(const Singleton &) = delete; //拷⻉构造
Singleton& operator=(const Singleton&) =
delete;//拷贝赋值构造
Singleton(Singleton &&) = delete;//移动构造
Singleton& operator=(Singleton &&) =
delete;//移动拷贝构造
static Singleton * _instance;
};
Singleton* Singleton::_instance = nullptr;//静态成员需要初始化
// 还可以使⽤ 内部类,智能指针来解决;此时还有线程安全问题
主要解决内存泄漏问题。
解决多线程的线程安全问题。
#include <mutex>
class Singleton { // 懒汉模式 lazy load
public:
static Singleton * GetInstance() {
// std::lock_guard<std::mutex>
lock(_mutex); // 3.1 切换线程
if (_instance == nullptr) {
std::lock_guard<std::mutex>
lock(_mutex); // 3.2
if (_instance == nullptr) {
_instance = new Singleton();
// 1. 分配内存
// 2. 调用构造函数
// 3. 返回指针
// 多线程环境下 cpu reorder操作
atexit(Destructor);
}
}
return _instance;
}
private:
static void Destructor() {
if (nullptr != _instance) {
delete _instance;
_instance = nullptr;
}
}
Singleton(){}; //构造
~Singleton(){};
Singleton(const Singleton &) = delete; //拷⻉构造
Singleton& operator=(const Singleton&) =delete;//拷贝赋值构造
Singleton(Singleton &&) = delete;//移动构造
Singleton& operator=(Singleton &&) =delete;//移动拷贝构造
static Singleton * _instance;
static std::mutex _mutex;
};
Singleton* Singleton::_instance = nullptr;//静态成员需要初始化
std::mutex Singleton::_mutex; //互斥锁初始化
通过双重检测(double check)提升效率。 虽然加了锁,但是没有考虑到CPU的指令重排。new操作符在汇编中是有多个指令构成的,它会做这些工作:(1)分配内存、(2)调用构造函数、(3)返回指针;在多核环境下,CPU会进行指令重排(即不是按照我们规定的顺序1、2、3进行),这可能会造成程序奔溃。而这里的加锁,是从单线程的语义来的。
C++ 98是单线程语义,在多核时代,C++11在C++98的基础上做了一些优化(mutex、atomic、内存栅栏),编译器重排、CPU重排会违反顺序一致性。会产生可见性问题和执行序问题。 为解决这些问题,C++ 11提供了同步原语:原子变量和内存栅栏。
#include <mutex>
#include <atomic>
class Singleton{
public:
static Singleton *GetInstance(){
Singleton *tmp=_instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);//获取内存屏障
if(tmp==nullptr)
{
std::lock_guard<std::mutex> lock(_mutex);
tmp=_instance.load(std::memory_order_relaxed);
if(tmp==nullptr){
tmp= new Singleton;
std::atomic_thread_fence(std::memory_order_release);// 释放内存屏障
_instance.store(tmp,std::memory_order_relaxed);
atexit(Destructor);
}
}
return tmp;
}
private:
static void Destructor(){
Singleton* tmp=_instance.load(stdd::memory_order_relaxed);
if(nullptr!=tmp)
delete tmp;
}
Singleton(){};
~Singleton(){};
Singleton(const Singleton &) = delete;
Singleton& operator=(const Singleton &) = delete;
Singleton(Singleton &&)=delete;
Singleton& operator=(Singleton &&)=delete;
static std::atomic<Singleton*> _instance;
static std::metex _mutex;
};
std::atomic<Singleton*> Singleton::_instance;//静态成员变量需要初始化
std::mutex Singleton::_mutex;//互斥锁初始化
// 编译
// g++ Singleton.cpp -o singleton -std=c++11
使用原子变量,原子变量解决了三个问题: (1)原子执行的问题。 (2)可见性问题。使用load(可以看见其他线程最新操作的数据)和和store(修改数据让其他线程可见)来解决。 (3)执行绪问题。使用内存模型解决,memory_order_acquire、memory_order_release
上述代码略显赋值,所以可以做一下优化。c++11 magic static 特性:如果当变量在初始化的时候,并发同时进⼊声明语句,并发线程将会阻塞等待初始化结束。
// c++ effective
class Singleton
{
public:
static Singleton& GetInstance() {
static Singleton instance;
return instance;
}
private:
Singleton(){}; //构造
~Singleton(){};
Singleton(const Singleton &) = delete; //拷⻉构造
Singleton& operator=(const Singleton&) =delete;//拷贝赋值构造
Singleton(Singleton &&) = delete;//移动构造
Singleton& operator=(Singleton &&) =delete;//移动拷贝构造
};
// 继承 Singleton
// g++ Singleton.cpp -o singleton -std=c++11
该版本具备 版本4 所有优点:
添加多态。通过friend class让子类能够访问父类的private。
template<typename T>
class Singleton {
public:
static T& GetInstance() {
static T instance; // 这⾥要初始化DesignPattern,需要调⽤DesignPattern 构造函数,同时会调⽤⽗类的构造函数。
return instance;
}
protected:
virtual ~Singleton() {}
Singleton() {} // protected修饰构造函数,才能让别⼈继承
private:
Singleton(const Singleton &) = delete; //拷⻉
构造
Singleton& operator=(const Singleton&) =delete;//拷贝赋值构造
Singleton(Singleton &&) = delete;//移动构造
Singleton& operator=(Singleton &&) =delete;//移动拷贝构造
};
class DesignPattern : public Singleton<DesignPattern> {
//friend 能让Singleton<T> 访问到 DesignPattern构造函数
friend class Singleton<DesignPattern>;
private:
DesignPattern() {}
~DesignPattern() {}
};
解决变化点问题,如果项目中有多个类都要写单例模式,通过继承的方式进行扩展。使用模板和友缘类。
(1)通过六个示例描述一步步完善单例模式的设计过程,需要考虑的问题。 (2)C++类的构造有:构造函数、拷贝构造、拷贝赋值构造、移动构造、移动赋值构造。 (3)编写单例模式代码时,需要考虑其线程安全性问题。 (4)同一个对象,它们是friend class的关系,可以互相访问私有成员。(5)单例模式是很常见的设计模式,需要掌握。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。