C++之特殊类的设计(单例模式)
Posted 小赵小赵福星高照~
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++之特殊类的设计(单例模式)相关的知识,希望对你有一定的参考价值。
特殊类的设计(单例模式)
文章目录
请设计一个类,只能在堆上创建对象
方法一
正常情况下我们既可能在栈上有可能在堆上创建对象,现在要求只能在堆上创建对象,我们可以将析构函数设置成私有,当在栈上创建对象后,最后释放对象时会报错,因为析构函数是私有的,那么有人说堆上的对象也要调用析构函数呀,我们的解决办法是加一个公有的成员函数专门来delete,成员函数是可以访问私有的,故我们在外面释放堆上的对象时可以调用这个公有的接口
实现如下:
#include<iostream>
using namespace std;
class OnlyHeap
public:
void DestoryObj(OnlyHeap* ptr)
delete ptr;//类里面可以访问私有
void DestoryObj()
delete this;//类里面可以访问私有
private:
~OnlyHeap()
cout<<"~OnlyHeap()"<<endl;
int _a;
;
int main()
OnlyHeap oh;//报错,在析构时没有不能访问私有
OnlyHeap *ptr = new OnlyHeap;
ptr->DestoryObj();
return 0;
方法二
也可以将构造函数私有:
- 构造函数私有化
- 提供一个公有函数创建对象,对象创建都在堆上
- 公有函数设置成静态可以突破类域解决问题
#include<iostream>
using namespace std;
class OnlyHeap
public:
//2. 提供一个公有函数创建对象,对象创建都在堆上
OnlyHeap* CreateObj()
return new OnlyHeap;//类里面可以访问私有
//设置成静态可以解决问题:
static OnlyHeap* CreateObj()
return new OnlyHeap;//类里面可以访问私有
private:
//1. 构造函数私有化
OnlyHeap()
cout<<"OnlyHeap()"<<endl;
int _a;
;
int main()
OnlyHeap oh;//报错,在构造时没有不能访问私有
OnlyHeap *ptr = OnlyHeap::CreateObj();//调用非静态成员函数需要实例化对象,突破类域就可以,成员函数设置成静态的,不然会报错
ptr->DestoryObj();
return 0;
但是上面的写法还是有缺陷:
OnlyHeap copy(*ptr);//拷贝构造
我们可以通过拷贝构造创建栈对象,所以我们可以将拷贝构造设置成私有:
-
C++98中将拷贝构造设置成私有称为防拷贝:只声明,不实现,声明成私有
-
C++11,可以将拷贝构造声明如下:
```cpp OnlyHeap(const OnlyHeap& oh) = delete; ```
//可以将拷贝构造设置成私有
#include<iostream>
using namespace std;
class OnlyHeap
public:
//2. 提供一个公有函数创建对象,对象创建都在堆上
OnlyHeap* CreateObj()
return new OnlyHeap;//类里面可以访问私有
//设置成静态可以解决问题:
static OnlyHeap* CreateObj()
return new OnlyHeap;//类里面可以访问私有
private:
//1. 构造函数私有化
OnlyHeap()
cout<<"OnlyHeap()"<<endl;
//C++98 防拷贝 -- 只声明,不实现,声明成私有
OnlyHeap(const OnlyHeap& oh);//没必要去实现拷贝构造
//这样做就不会生成默认的拷贝构造函数
//or
//C++11
OnlyHeap(const OnlyHeap& oh) = delete;
int _a;
;
int main()
OnlyHeap oh;//报错,在构造时没有不能访问私有
OnlyHeap *ptr = OnlyHeap::CreateObj();//调用非静态成员函数需要实例化对象,突破类域就可以,成员函数设置成静态的,不然会报错
ptr->DestoryObj();
return 0;
如果是上面实现的话,就不用考虑防止拷贝构造了,因为拷贝构造出来最后也需要调用析构函数,因为析构是私有,所以也会报错,我们主要需要理解第二种方式,因为设计思路非常通用。
请设计一个类,只能在栈上创建对象
方法一
通过上面的学习,相信这个问题就变得简单了,解决方法为:将构造函数私有化,给一个公有的静态成员函数在栈上创建对象
class StackOnly
public:
static StackOnly CreateObj()
return StackOnly();
private:
StackOnly()
:_a(0)
cout<<"StackOnly()"<<endl;
int _a;
int main()
StackOnly so;//不能创建
StackOnly* ptr = new StackOnly;//不能创建
StackOnly::CreateObj();//突破类域去调用静态成员函数在栈上创建对象
return 0;
方法二
还有一种方法是重载一个类专属的operator new,将它设置成私有,因为new创建对象时会调用operator new申请空间。
class StackOnly
public:
StackOnly()
:_a(0)
cout<<"StackOnly()"<<endl;
private:
//重载一个类专属的operator new
//只声明
//C++98 防调用 -- 只声明不实现,声明为私有
void* operator new(size_t size);
void* operator delete(void* ptr);
//C++11
void* operator new(size_t size) = delete;
void* operator delete(void* ptr) = delete;
int _a;
int main()
StackOnly so;
StackOnly* ptr = new StackOnly;
return 0;
我们这里将operator new限制住,这种方式存在一些漏洞,因为无法禁止在静态区创建对象:
static StackOnly sso;
请设计一个类,不能被拷贝
拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类
不能调用拷贝构造函数以及赋值运算符重载即可。
C++98
C++98的实现方式:
class CopyBan
// ...
private:
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
//...
;
为什么设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝
了为什么只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简
单,而且如果定义了就不会防止成员函数内部拷贝了。
C++11
C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表
示让编译器删除掉该默认成员函数。
class CopyBan
// ...
CopyBan(const CopyBan&)=delete;
CopyBan& operator=(const CopyBan&)=delete;
//...
;
请设计一个类,不能被继承
C++98中我们将父类构造函数设置成私有,父类可以使用静态成员函数创建对象,这样可以解决这个问题,但是这种方式不够直接,没有直接禁止继承,这里其实是可以继承的,但是Derive不能创建对象,因为Derive的构造函数必须要调用父类NonInherit的构造函数,但是NonInherit构造函数是私有,在子类中不可见,那么这里继承不会报错,继承的子类创建对象就会报错
class NonInherit
public:
//父类可以使用静态成员函数创建对象
static NonInherit GetInstance()
return NonInherit();
private:
//构造函数私有
NonInherit()
;
//C++98 这种方式不够直接,没有直接禁止继承
//这里是可以继承的,但是Derive不能创建对象,因为Derive的构造函数必须要调用父类NonInherit的构造函数,但是NonInherit构造函数是私有,在子类中不可见,那么这里继承不会报错,继承的子类创建对象就会报错
class Derive : NonInherit
;
int main()
return 0;
C++11的实现方法:我们可以在父类后面加一个关键字final,这样直接就禁止了继承
class NonInherit final
;
class Derive : NonInherit
;
int main()
return 0;
C++11的不能被继承的方式,简单明了直接
请设计一个类,只能创建一个对象(单例模式)
设计模式
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
单例模式
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
单例模式的本质是去管理全局对象,定义一个全局对象,大家都能用也能保证单例,但是这种方式存在很大的缺陷,你要让大家都能用,这个对象你只能定义在一个.h,如果这个.h在包含在多个.cpp,链接会报错
Singleton.h
#include<vector>
//全局定义一个,大家都用它
std::vector<int> v;
void f1();
Singleton.cpp
//.h
#include"Singleton.h"
void f1()
v.push_back(1);
v.push_back(2);
v.push_back(3);
for(auto e :v)
cout<< e <<" ";
cout<<endl;
test.cpp
#include<Singleton.h>
//.h
void f2()
v.push_back(11);
v.push_back(22);
v.push_back(33);
for(auto e :v)
cout<< e <<" ";
cout<<endl;
int main()
f1();
f2();
return 0;
这样编译会报错,为什么呢?全局链接时会发现有两个命名v,会产生二义性,所以会报错,为了不报错,我们这样解决:
#include<vector>
void f1();
static std::vector<int> v;
将v定义成全局静态,每个文件独自拥有v,但是不再是同一个对象,每个.cpp中各自是一个对象,但是这样会有多个vector对象了,那么怎么让不同的CPP访问到同一个vector呢?
将声明和定义分离,在.h里面声明,在.cpp里面定义就可以解决
注意不能在.h里面定义,这样的话所有包了头文件的地方都会有定义,这就重复定义了
.h文件对他声明:
#include<vector>
#nclude<iostream>
using namespace std;
//全局定义一个,大家都用它
std::vector<int> v;
void f1();
//static vector<int> v;
extern vector<int> v;//声明
extern关键字可以置于变量或函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其它模块寻找其定义
.cpp文件进行定义:
#include<Singleton.h>
//.h
void f1()
v.push_back(11);
v.push_back(22);
v.push_back(33);
for(auto e :v)
cout<< e <<" ";
cout<<endl;
//定义
vector<int> v;
这样此时只会有一个vector的对象了。
单例模式的实现
全局只有唯一的实例对象,那么他里面的成员也就是单例的。static修饰成员变量保证了全局只有一个唯一一个。
饿汉模式
饿汉模式:在进入main函数之前就创建对象
- 构造函数私有化,保证不能随意创建对象
- 类里面声明一个static Singleton对象(static修饰的成员全局就一个),在.cpp文件定义这个对象,声明和定义分离
- 提供一个获取单例对象的static成员函数
定义需要放在.cpp文件中:
//定义
Singleton Singleton::_sinst;
//获取单例对象的static成员函数的实现
Singleton& Singleton::GetInstance()
return _sinst;
.h文件
class Singleton
public:
//3、提供一个获取单例对象的static成员函数
static Singleton& GetInstance();
//获取实例对象
//如果vector对象是私有,想访问,只能再封装一层
void PushBack(int x)
_v.push_back(x);
private:
//1、构造函数私有化,不能随意创建对象
Singleton()
private:
vector<int> _v;
//2、类里面声明一个static Singleton对象,在cpp定义这个对象、
//保证了全局只有一个唯一对象
static Singleton _sinst;
;
int main()
Singleton::GetInstance();//这样就获取到了这个对象
return 0;
但是我们上面设置的类还有缺陷,当前类可以拷贝构造,所以将拷贝构造也设置成私有:
//防拷贝
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
饿汉模式缺陷:单例对象是main函数之前创建初始化的
-
如果单例对象的构造函数中要做很多工作,可能会导致程序启动慢
-
如果多个单例类,并且它们之间有依赖关系,那么饿汉模式无法保证
假设有三个单例类,要求三个之间有依赖关系,需要Singleton1先生成才能有Singleton2,需要有Singleton2才能有Singleton3:
那么饿汉模式无法保证,全局的静态变量在初始化时不能保证谁先初始化谁后初始化,为了更好的控制这些问题,有人就给出懒汉模式:
懒汉模式
第一次调用GetInstance时,才会创建初始化单例对象,相对于饿汉,不存在可能会导致启动慢的问题,也可以控制顺序依赖的问题了
.cpp文件里定义:
//定义
Singleton* Singleton::_spinst = nullptr;
Singleton& Singleton::GetInstance()
if(_spinst == nullptr)
_spinst = new Singleton;
return *_spinst;
//获取实例对象
class Singleton
public:
//3、提供一个获取单例对象的static成员函数
static Singleton& GetInstance();
//获取实例对象
//如果vector对象是私有,想访问,只能再封装一层
void PushBack(int x)
_v.push_back(x);
private:
//1、构造函数私有化,不能随意创建对象
Singleton()
private:
vector<int> _v;
//2、类里面声明一个static Singleton对象,在cpp定义这个对象、
//保证了全局只有一个唯一对象
static Singleton* _spinst;
;
我们现在这样写还是有问题的,有线程安全问题。假设有t1,t2两个线程,当_spinst不为nullptr,t1线程进去还没创建对象,而t2线程来了,此时t2也判断_spinst不为nullptr,也去创建对象了,此时就保证不了单例
所以我们需要再给一个成员mutex进行加锁:
class Singleton
public:
//3、提供一个获取单例对象的static成员函数
static Singleton& GetInstance();
//获取实例对象
//如果vector对象是私有,想访问,只能再封装一层
void PushBack(int x)
_v.push_back(x);
private:
//1、构造函数私有化,不能随意创建对象
Singleton()
private:
vector<int> _v;
//2、类里面声明一个static Singleton对象,在cpp定义这个对象、
//保证了全局只有一个唯一对象
static Singleton* _spinst;
static mutex _mtx;//线程操作
;
需要注意的是_mtx在.cpp文件中定义,不然链接时会出错:
//定义
Singleton* Singleton::_spinst = nullptr;
mutex Singleton:: _mtx;
//加锁保证在多线程环境下面,一定只有一个线程区new对象,只创建出一个单例对象
Singleton& Singleton::GetInstance()
_mtx.lock();
if(_spinst == nullptr)
_spinst = new Singleton;
_mtx.unlock();
return *_spinst;
但是这样加锁后还会有问题,我们访问这段代码有两种情况:
1、同时有多个线程进来
2、不同时
但是当_spinst不为nullptr了以后,其他线程来了还是要加锁解锁,效率低,那么能这样加锁吗?
//定义
Singleton* Singleton::_spinst = nullptr;
mutex Singleton:: _mtx;
//加锁保证在多线程环境下面,一定只有一个线程区new对象,只创建出一个单例对象
Singleton& Singleton::GetInstance()
if(_spinst == nullptr)
_mtx.lock();
_spinst = new Singleton;
_mtx.unlock();
return *_spinst;
不能,这样会存在线程安全问题,当第一个线程来了后,_spinst不为空,进去加锁创建对象了,在第一个线程还没有创建对象时,第二个线程来了,也判断_spinst不为空,但是此时有锁他进不去,当第一个线程创建完线程后解锁后,第二个线程也来了,但是此时_spinst已经不为空,再创建就有问题了
所以我们进行双检查提高效率,保证了除了第一个线程需要获取锁之外,其他线程都不需要获取锁,直接返回对象,既能保证线程安全又能提高效率
//定义
Singleton* Singleton::_spinst = nullptr;
mutex Singleton:: _mtx;
//加锁保证在多线程环境下面,一定只有一个线程区new对象,只创建出一个单例对象
Singleton& Singleton::GetInstance()
//双检查
if(_spinst == nullptr)
_mtx.lock();
if(_spinst == nullptr)
_spinst = new Singleton;
_mtx.unlock();
reurn *_spinst;
懒汉模式完整代码:
class Singleton
public:
//3、提供一个全局访问点获取单例对象
static Singleton* GetInstance()
//双检查
if (_inst == nullptr)
_mtx.lock();
if (_inst == nullptr)
_inst = new Singleton;
_mtx.以上是关于C++之特殊类的设计(单例模式)的主要内容,如果未能解决你的问题,请参考以下文章