目录
1.创建一个只能在堆上创建的类
根据要求,在堆上创建的类,所以只能通过new去创建新的对象
class HeapOnly {
public:
static HeapOnly* HeapOnly_Init() {
return new HeapOnly();
}
private:
HeapOnly() {
}
HeapOnly(const HeapOnly& _hp) = delete;
HeapOnly& operator=(const HeapOnly& hp) = delete;
};
我们将构造函数封死,注意不是delete。
拷贝构造函数必须封死,如果不封死的话,我们便可以先创建一个堆上的对象,然后用堆上的对象通过拷贝构造出来栈上的对象
HeapOnly* hp = HeapOnly::HeapOnly_Init();
HeapOnly hp1 = *hp;
赋值重载最好也需要封死。
这样,我们在创建对象的时候只能通过调用HeapOnly_Init()去创建。
并且,由于我们是在 HeapOnly_Init()内返回的是对象的地址,我们没有手动调用析构函数,所以这个地址所指向的空间不会被释放,返回给接收者后,也不会被释放。
2.创建一个只能在栈上开辟空间的对象
根据要求,在栈上创建的类,它不应支持new对象操作。
class StackOnly {
public:
static StackOnly creatObj() {
return StackOnly();
}
private:
StackOnly() {
}
};
StackOnly x = StackOnly::creatObj();
static StackOnly cp = x;该代码的第二行会在静态区开辟空间,不符合我们的要求,原因是他用调用了拷贝构造,拷贝了一份。但是这里如果直接把拷贝构造封掉delete,那么第一行代码就会出现问题,因为第一行返回的时候,会调用拷贝构造创建临时变量,然后将这份临时变量再拷贝给x。
所以会调用拷贝构造。
那怎么解决上述问题 ?
既然它返回的是临时变量,那么我们是不是可以使用右值引用?
class StackOnly {
public:
static StackOnly creatObj() {
return StackOnly();
}
StackOnly(const StackOnly&& p) {//支持你移动构造
//但是我们必须声明为public类型,在privete下不支持调用
}
private:
StackOnly() {
}
StackOnly(const StackOnly& p) = delete;
};
StackOnly x = StackOnly::creatObj();
static StackOnly cp = move(x);但是我这里使用move,也无法根治这个问题。
所以创建一个只能在栈上开辟空间的对象,是无法很好的完成的
3.单例模式
3.1饿汉模式
饿汉模式,首先是单例的,即全局只能创建一个对象
class singleton {
public:
static singleton* getInstance() {
return &_Singleton;
}
private:
singleton(int value):
_value(value)
{}
singleton(const singleton& _s) = delete;
singleton&operator=(const singleton&_s) = delete;
static singleton _Singleton;//这里定义为类型也行,也可以定义为指针
//static singletion *_sig;
//参数
int _value;
};
singleton singleton::_Singleton(10);//类外初始化
//singletion* singletion::_sig=new signleton();
int main() {
}
饿汉模式中的类定义了一个私有静态的的类对象,静态保证了它能做到全局为同一份。
然后提供了一个静态的getInstance()用于获取该静态类的静态成员变量。即获取该对象的本身。
另外饿汉模式本身是线程安全的,因为它会在进入main函数之前就初始化。所以不涉及到线程安全问题,但它如果设计到其他操作,那么其他操作则不是线程安全的,因此需要加锁,确保线程的安全。如下为示例代码:
class singleton {
public:
static singleton* getInstance() {
return _Singleton;
}
void pushBack(int value) {
_vmux.lock();
_v.push_back(value);
_vmux.unlock();
}
private:
singleton():
_v()
{}
singleton(const singleton& _s) = delete;
singleton&operator=(const singleton&_s) = delete;
static singleton *_Singleton;
mutex _vmux;
//参数
vector<int> _v;
};
3.2懒汉模式
懒汉模式是在需要的时候,才会构造出来类,而不是一开始就构造出来
class Singleton {
public:
static Singleton* GetInstance() {
if (_sl==nullptr) {//为了避免频繁加锁,因为只要创建对象后,就不用加锁去保护创建对象了
_slmux.lock();
if (_sl==nullptr) {//这里如果不加锁,会存在线程安全问题
_sl = new Singleton();
}
_slmux.unlock();
}
return _sl;
}
//由于我们是通过new创建出来的类,出了作用域不会自动调用析构函数,直到程序结束
//所以我们可以给出销毁对象的函数
static void Destory() { //这个地方必须为静态的,因为我们下方使用GC的时候,才可以不使用类实例去访问
_slmux.lock();
if (_sl != nullptr) {
delete _sl;
}
_slmux.unlock();
}
//如果不想手动释放,那么就使用内部类做回收
class GC {
public:
~GC()
{
Destory();
}
};
static GC _gc;
void pushBack(int value) {
_vmux.lock();
_v.push_back(value);
_vmux.unlock();
}
~Singleton()
{
//
}
private:
Singleton(){}
Singleton(const Singleton&sl) {}
Singleton& operator=(const Singleton&sl){}
static Singleton* _sl;
vector<int> _v;
static mutex _slmux;//由于该锁用在静态对象,所以需要使用静态锁
mutex _vmux;
};
mutex Singleton::_slmux;
Singleton* Singleton::_sl = nullptr;
Singleton::GC Singleton::_gc;
int main() {
Singleton::GetInstance()->pushBack(1);
Singleton::GetInstance()->Destory();
return 0;
}
1. GetInstance()函数里面采用双重if,用来避免频繁的加锁、解锁
2.设有手动释放资源的函数,该函数需要为静态的函数,方便我们下方的GC垃圾回收机制
3.使用内部类做垃圾回收机制GC ,利用类(不是new出来的)离开作用域的时候会自动调用析构函数进行析构的这一特性,自动析构
4.类里面的静态成员变量也是全局的,只不过被封在类域里面了但是类成员函数里面的静态变量则不是全局的。
3.饿汉与懒汉的对比:
3.1线程安全问题
饿汉会在一开始,进入main函数之前就创建好了,所以饿汉本身不存在线程安全
懒汉会在我们调用它的时候进行初始化,所以懒汉需要对本身加锁,来保证自身的线程安全问题。
饿汉与懒汉所管理的资源不是线程安全的,但是我们可以对其加锁来保证线程安全
3.2顺序问题
由于饿汉是在一开始就创建出来的,现在有两个饿汉单例,有依赖关系,要求饿汉1先创建,饿汉2后创建,饿汉无法控制。但是懒汉可以控制。
3.3饿汉的缺点
如果单例对象很大,初始化很慢(初始化动作多,还会伴随一些IO行文,如读取配置文件),main函数之前就要创建,影响程序的启动速度,不支持控制依赖关系。
3.3 饿汉模式的另一种写法
class Singleton {
public:
static Singleton* GetInstance() {
static Singleton s;
return &s;
}
private:
Singleton() {};
Singleton(const Singleton& p)=delete;
~Singleton() {}
static Singleton _sg;
};
Singleton Singleton::_sg;
这种写法在单线程下没有问题。
但是在多线程下,在c++11之前存在线程安全问题,c++11之后则不存在线程安全问题
线程安全问题,在于调用GetInstance()创建static Singleton s;时存在的线程安全问题。