目录
(2)数据库管理和缓存类,都设计成单例,要求先初始化数据库对象,再初始化缓存对象
假设文件销毁时需要把信息写到文件持久化,但是指针又不自动调析构函数
一.前几个特殊类设计
1.请设计一个类,不能被拷贝
这种防拷贝需求例如:
unique_ ptr
thread(线程)
mutex (锁)
istream(IO流对象)
ostream
class CopyBan
{
// ...
private:
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
//...
};
class CopyBan
{
// ...
CopyBan(const CopyBan&)=delete;
CopyBan& operator=(const CopyBan&)=delete;
//...
};
2. 请设计一个类,只能在堆上创建对象
我们通常创建对象时,对象调用构造函数后创建在这三个区域:
class HeapOnly
{}
HeapOnly h1; 栈
static HeapOnly h2; 静态区
HeapOnly* ph3 = new HeapOnly; 堆区
3.拷贝构造需要防止:Heap0nly copy( *ph4) ; 指针ph4对应的对象虽然在堆上,但是拷贝后copy这个对象在栈上,所以要用delete删除拷贝构造函数。
4.赋值重载可以不禁:拷贝构造是把已构造对象拷贝给一个未构造的对象,赋值是把两个已经构造的函数互相赋值,这里构造只能再堆上,互相赋值仍是在堆上。
3. 请设计一个类,只能在栈上创建对象
这里的拷贝构造依然可以去掉,因为CreateObj()中的 return StackOnly(); 先构造一个匿名对象,再拷贝构造一个临时变量,临时变量再拷贝构造给h1,被编译器优化为一次构造,直接构造h1
class StackOnly
{
public:
static StackOnly CreateObj()
{
return StackOnly();
//StackOnly st;
//return st;
}
StackOnly(const StackOnly&) = delete;
//void Print()
//{
// cout << "Stack Only" << endl;
//}
private:
// 构造函数私有
StackOnly()
{}
};
int main()
{
StackOnly h1 = StackOnly::CreateObj();
//StackOnly::CreateObj().Print();
//static StackOnly h2;
//StackOnly* ph3 = new StackOnly;
return 0;
}
4. 请设计一个类,不能被继承
C++98方式
// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class NonInherit
{
public:
static NonInherit GetInstance()
{
return NonInherit();
}
private:
NonInherit()
{}
};
C++11方法
class A final
{
....
};
二.单例模式:请设计一个类,只能创建一个对象(单例模式)
1.饿汉模式
(1)介绍
③注意: Singleton* Singleton::_spInst = new Singleton; 类外定义可以访问类中的私有(构造),因为我们指定了_spInst属于Singleton这个类。
// 适配器模式/单例模式/迭代器模式 扩展学习:观察者模式/工厂模式
// 饿汉 -- 一开始(main函数之前)就创建
class Singleton
{
public:
//GetInstance()不能通过对象方式访问,只能通过类的方式访问,所以要加static
static Singleton* GetInstance()
{
return _spInst;
}
void Print();
private:
Singleton()
{}
Singleton(const Singleton&) = delete;
//static Singleton _sInst; // 声明
static Singleton* _spInst; // 声明
int _a = 0;
};
//Singleton Singleton::_sInst; // 定义
Singleton* Singleton::_spInst = new Singleton; // 定义
void Singleton::Print()
{
cout << _a << endl;
}
int main()
{
// GetInstance()可以获取到这个Singleton类的单例对象
Singleton::GetInstance()->Print();
//Singleton st1; 无法创建对象
//Singleton* st2 = new Singleton; 无法创建对象
//Singleton copy(*Singleton::GetInstance()); 无法创建对象
return 0;
}
(2)实例:维护一份地址和秘钥
想维护一份地址和秘钥,仅仅只有一份,假如一个线程创建了地址和秘钥,其他线程读到的地址和秘钥都是这一份。(如果能定义多个对象,就无法保证信息唯一)
想让进程中某个信息只有一份,就可以设为单例。
InfoMgr -- 单例
class InfoMgr
{
public:
static InfoMgr* GetInstance()
{
return _spInst;
}
void SetAddress(const string& s) //修改“_address”这个信息的函数
{
_address = s;
}
string& GetAddress()
{
return _address;
}
private:
InfoMgr()
{}
InfoMgr(const InfoMgr&) = delete;
string _address;
int _secretKey;
static InfoMgr* _spInst; // 声明
};
InfoMgr* InfoMgr::_spInst = new InfoMgr; // 定义
int main()
{
//InfoMgr info1;
//static InfoMgr info2;
//InfoMgr info3;
// 全局只有一个InfoMgr对象
InfoMgr::GetInstance()->SetAddress("陕西省西安市雁塔区");//创建一份信息
cout << InfoMgr::GetInstance()->GetAddress() << endl;//访问都是这一份信息
return 0;
}
(3)new的不需要delete释放吗?
2.懒汉模式
(1)和饿汉的区别:
②懒汉第一次初始化时 if (_spInst == nullptr) 要加锁,因为如果是多线程同时执行,就会生成多个对象;饿汉不需要加锁,因为他在main函数之前就把静态的单例对象初始化出来了,在main函数之前只有主线程,没有新线程。
懒汉—— 一开始不创建对象,只是在获取单例对象时需要调用函数GetInstance时再创建对象
和饿汉相比,饿汉会初始化对象:Singleton* Singleton::_spInst = new Singleton;
懒汉不会初始化对象,而是会给成空指针:Singleton* Singleton::_spInst = nullptr;
并且因为静态对象都是main函数之前初始化的,所以懒汉只能给空指针。直接定义对象都是饿汉Singleton Singleton::_sInst; ,因为main函数之前对象就已经创建初始化了。
// 懒汉 -- 一开始不创建对象,第一调用GetInstance再创建对象
class InfoMgr
{
public:
//GetInstance()不能通过对象方式访问,只能通过类的方式访问,所以要加static
static InfoMgr* GetInstance()
{
// 还需要加锁,这个后面讲 -- 双检查加锁
if (_spInst == nullptr) //只在第一次初始化
{
_spInst = new InfoMgr;
}
return _spInst;
}
void SetAddress(const string& s)
{
_address = s;
}
string& GetAddress()
{
return _address;
}
// 实现一个内嵌垃圾回收类
//为什么不能直接把析构函数设为公有,在析构函数内设置delete?
//——因为你的static对象(static InfoMgr* _spInst)是指针,指针不会自动调用析构,
class CGarbo {
public:
~CGarbo() {
if (_spInst)
delete _spInst;
}
};
// 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象
static CGarbo Garbo;
private:
InfoMgr()
{}
~InfoMgr()
{
// 假设析构时需要信息写到文件持久化
}
InfoMgr(const InfoMgr&) = delete;
string _address; ————————————成员数据
int _secretKey;
static InfoMgr* _spInst; // 声明
};
InfoMgr* InfoMgr::_spInst = nullptr; // 定义
InfoMgr::CGarbo Garbo;
int main()
{
// 全局只有一个InfoMgr对象
InfoMgr::GetInstance()->SetAddress("陕西省西安市雁塔区");
cout << InfoMgr::GetInstance()->GetAddress() << endl;
return 0;
}
(2)数据库管理和缓存类,都设计成单例,要求先初始化数据库对象,再初始化缓存对象
单例:SQLMgr
单例:CacheMgr
饿汉控制不住,可能会控制不住顺序而报错——饿汉对象的初始化顺序不确定
懒汉可以控制
SQLMgr::GetInstance()
CacheMgr:GetInstance()
总结一下:
饿汉特点:简单一点、初始化顺序不确定,如果有依赖关系就会有问题。饿汉对象初始化慢且多个饿汉单例对象会影响程序启动
懒汉特点:复杂一点。第一次调用时初始化,可以控制初始化顺序,延迟加载初始化,不影响程序启动
假设文件销毁时需要把信息写到文件持久化,但是指针又不自动调析构函数
这个写到文件持久化 的功能在 ~InfoMgr()中实现,但是 你的static对象是指针,指针又不自动调析构函数,就需要实现一个内嵌垃圾回收类,定义这个类的对象,这个内部类的对象结束时调用内部类的析构函数,内部类的析构函数会delete _spInst,delete _spInst就会去调用 ~InfoMgr() ,完成写入文件
class InfoMgr
{
public:
~InfoMgr()
{
// 假设析构时需要信息写到文件持久化
}
// 实现一个内嵌垃圾回收类
class CGarbo {
public:
~CGarbo() {
if (_spInst)
delete _spInst;
}
};
// 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象
static CGarbo Garbo;
}
InfoMgr::CGarbo Garbo;
为什么不能直接把析构函数设为公有,在析构函数内设置delete?
你的static对象是指针,指针又不会调用析构,所以得借助一个其他类对象析构,反过来调。