目录
一,只在堆上创建对象的类
1,实现方式
二,只在栈上创建对象的类
三,不能被继承的类
四,只能创建一个对象的类(单例模式)
4.1 饿汉模式
4.2,懒汉模式
五,懒汉模式与饿汉模式对比
概述:堆上开辟的空间是使用malloc new创建的空间,所以我们设计的类只能用new或者malloc开辟空间。
将构造函数私有化,拷贝构造私有,赋值也设计成私有,避免在栈上开辟空间,使用静态成员函数调用构造函数,完成对象的创建。
class HeapOnly
{
public:
static HeapOnly*CreatInstance()
{
return new HeapOnly();
}
//将构造函数私有化,创建静态对象构造对象
private:
HeapOnly()
{}
HeapOnly(const HeapOnly&)=delete;
HeapOnly& operator =(const HeapOnly&)=delete;
};
概述:栈由操作系统自动分配释放 ,用于存放函数的参数,所以不能使用new和malloc等开辟空间的做法,通过使用系统默认的变量来存放数据。
1,实现方式:构造函数放在私有成员里面,通过静态成员函数创建返回对象即可
// 构造放私有,不适用new和malloc
class StackOnly
{
public:
static StackOnly CreatObj()
{
return StackOnly();
}
private:
StackOnly()
{}
//C++98,只声明,不实现
StackOnly(const StackOnly&);
StackOnly& operator=(const StackOnly&);
}
C++98中通过将构造函数放成私有的函数,就可以实现子类无法对父类进行构造,从而无法继承
class NonInherit
{
public:
static NonInherit Getinnstance()
{
return NonInherit();
}
private:
NonInherit()
:_a(0)
{}
int _a;
};
class Inherit:public NonInherit//不可继承
{
};
C++11中提供了关键字final来实现类不可继承
class A final
{
};
class B :public A
{
};
一个类只能创建一个对象,即单例模式,该模式可以保证在系统中只有一个此类对象,并提供一个全局的访问点,该实例被所有模块共享
在未进main函数之前,对对象进行实例化,并提供一个全局的访问点,
缺点:如果这个类要实例化的对象很多,则在程序启动的时候比较慢,迟迟不能进入main函数。
优点:在访问这个类之前已经初始化,访问时直接返回,避免了线程同时访问时,创建出两个对象,产生内存泄漏。
实现:构造函数私有定义,提供静态全局访问函数接口,返回静态对象指向的成员指针。
class HungryMan
{
public:
static HungryMan* GetInstance()
{
return _inst;
}
void Print()
{
cout << "Print()" << _a << endl;
}
private:
HungryMan()
:_a(0)
{}
//禁用拷贝构造和赋值
HungryMan(const HungryMan&)=delete;
HungryMan& operator=(const HungryMan&)=delete;
int _a;
//单例对象使用static,只创建一个
static HungryMan* _inst;
};
HungryMan* HungryMan::_inst=new HungryMan;
如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
优点:第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控制。
缺点:复杂
实现:此时为了调用对象时,初始化只能一次,为了防止线程同时调用,需要加互斥锁,在初始化时,加锁,初始化完成,解锁,从此以后,直接返回对象指针。
class Lazy
{
//如果多线程同时访问这个对象,可能存在都为空,然后同时创建,形成内存泄漏
//所以此时需要加锁
public:
static Lazy* GetInstance()
{
// 保护第一次需要加锁,后面都不需要加锁的场景,可以使用双检查加锁
// 特点:第一次加锁,后面不加锁,保护线程安全,同时提高了效率
if(_inst==nullptr)
{
_mtx.lock();//加锁
if(_inst==nullptr)
{
_inst=new Lazy;
}
_mtx.unlock();//解锁
}
return _inst;
}
void print()
{
cout<<_inst<<" "<<_a<
懒汉模式和饿汉模式的对比
饿汉
优点:简单
缺点:1、如果单例对象构造函数工作比较多,会导致程序启动慢,迟迟进不了入口main函数
2、如果有多个单例对象,他们之间有初始化依赖关系,饿汉模式也会有问题。
比如有A和B两个单例类,要求A单例先初始化,B必须在A之后初始化。那么饿汉无法保证
这种场景下面用懒汉就可以,懒汉可以先调用A::GetInstance(),再调用B::GetInstance().
懒汉
优点:解决上面饿汉的缺点。因为他是第一次调用GetInstance时创建初始化单例对象
缺点:相对饿汉,复杂一点点。