这个设计模式细节挺多,每个模式感觉好像能大致回想起来,但是总有遗漏,所以打算写一篇文章来复习一遍。
设计一个防拷贝的类
class CopyBan 不能被拷贝
{
public:
CopyBan()可以构造
{
cout<<"CopyBan()"<
还有种防拷贝的方式是如下的,
class CopyBan
{
private:
CopyBan(const CopyBan& fc);//防拷贝2,只声明,不实现
};
虽然外部调用拷贝构造时会出错,但要编译才报错,如果直接用delete就可以在写代码阶段就报红了
设为私有是为了防止外部调用,但防不住别人去实现,实现也没用
CopyBan::CopyBan(const CopyBan& fc)
{
cout<<"CopyBan::CopyBan(const CopyBan& fc)";
}
设计一个只能在栈上创建的类。
class StackOnly
{
StackOnly()
{
cout << "StackOnly()" << endl;
}
StackOnly(const StackOnly& s)拷贝构造可用于栈上创建对象
{
cout << "StackOnly(const StackOnly& s)" << endl;
}
void* operator new(size_t size) = delete;
new = operator new + 构造, new可以调用拷贝和普通构造, 所以只能封operator new
析构不能封
~StackOnly()
{
cout << "~StackOnly()" << endl;
}
析构函数一封,在栈上都无法创建对象,会直接报红
};
所以说new在最上层,其实是个关键字,可以申请空间加调用构造函数初始化,而内部封装了operator new这个函数,用于申请空间,调用构造函数有其它函数完成,此时我们在类内重载一个operator new,调用时先用类内的,而类内的已被删除,编译器选择直接报错,而不是去类外找全局的operator new,显示调用全局的用::operator new。?而且静态变量这个也防不住。
只能在堆上创建
class HeapOnly
{
public:
void Delete()
{
delete this;
cout << "Delete";
}
HeapOnly()
{
cout << "HeapOnly()" << endl;
}
HeapOnly(const HeapOnly& s)
{
cout << "HeapOnly(const HeapOnly& s)" << endl;
}
//封析构
private:
~HeapOnly()
{
cout<<"~HeapOnly()"<
方法2,在栈上创建对象无非就是用构造和拷贝构造函数,如果我们把这两个封了,就不能在栈上创建对象了。封了构造函数,那new也就没办法调用构造函数了,所以必须提供一个公有函数,在该函数内用new, 这样new可以调用构造函数,我们也可以调用这个公有函数来创建对象。好,问题来了,如何调用这个公有函数,用对象调,对象哪里来,调用Creat这个公有函数创建对象呗,这不就逻辑死循环了吗?所以这个公有函数就必须是静态的。析构就可以不用private修饰了。
class HeapOnly2
{
public:
static HeapOnly2* Creat()
{
return new HeapOnly2;
}
~HeapOnly2()
{
cout << "~HeapOnly2()" << endl;
}
private:
HeapOnly2()
{
cout << "HeapOnly2()" << endl;
}
HeapOnly2(const HeapOnly2& s)
{
cout << "HeapOnly2(const HeapOnly2& s)" << endl;
}
};
单例模式是指该类只实例化一个对象,那构造得设为private,免得让外部创建对象,那我们如何创建那个唯一的对象呢,如果创建一个全局对象,然后写一个静态函数Creat函数,返回这个对象的指针或者引用就好了,可是构造函数私有了,外部无法调用。所以还得在类内创建对象,大佬是将其设为静态成员变量。这样编译前sgl成员就会被创建。至于Creat函数是返回指针还是引用都可以。
class SingleHungry
{
public:
static SingleHungry* Creat()
{
return &sgl;
}
private:
析构可以设为似有,由于静态成员变量是类内的,可以调用private修饰的
~SingleHungry()
{
cout<<"~SingleHungry()"<
sg1属于类内的成员, 所以定义时可以调用private修饰的构造函数,原来我以为是这样解释的,可回想好像不太对,类内成员(例如存个该类类型的指针)在类外也调用不了private修饰的函数啊,可是当一个静态成员函数的定义和声明分离,静态成员函数的定义内是可以调用private修饰的成员函数的,所以我认为sg1可以调用构造函数的原因和这个是类似的,祖师爷也没办法了,只能做这种特殊处理了。
饿汉模式是定义一个静态变量,之后要使用这个类,就先调用一个Creat返回这个静态对象的指针或者引用,但是如果程序存在大量的静态变量,那在启动的时候就需要做非常多的初始化工作,一个程序启动可能要半个小时,而且最关键的是无法决定静态变量的初始化顺序,有时候B类依赖A类初始化,但是有可能B类静态对象初始化时,A类还未创建,这就出问题。
所以我们一开始就先不创建对象,而是定义一个静态对象的指针,而且把指针初始化为空,这个时候初始化指针还不简单吗。那什么时候创建对象呢?调用Creat函数的时候就创建对象给sgl指针。
这样也就可以确定静态对象的创建顺序了,可是还有个问题就是sgl析构的问题。这个指针指向的资源如何释放,有人说,没事调用析构函数,大坑 ! sgl是new返回的,之所以要调用delete有两个作用,一个是释放sgl指向的空间,还有就是调用析构函数释放SingleLazy的对象内部的资源,举个例子,例如 string* p new string ; delete不仅要释放p指向的这个string对象,析构函数是释放内部指针指向的资源的,所以不能sgl不能直接调用析构函数。要用delete。delete可以,但是有时候例如我们除了释放内存的资源外,可能还想将保存到磁盘上,那就不能单单写个delete,所以我们再写一个函数Destroy()去封装delete,并且在delete前把资源保存到磁盘。
class SingleLazy
{
public:
static SingleLazy* Creat()
{
sgl = new SingleLazy();
return sgl;
}
static void Destroy()
{
string s("he");
FILE* f = fopen("test.txt","w");
fwrite(s.c_str(),sizeof(string),1,f);
delete sgl;
}
private:
SingleLazy()
{
cout << "SingleLazy()" << endl;
}
~SingleLazy()
{
cout << "~SingleLazy" << endl;
}
SingleLazy(const SingleLazy& sh) = delete;
SingleLazy operator=(const SingleLazy& sh) = delete;
static SingleLazy* sgl;
};
SingleLazy* SingleLazy::sgl = nullptr;
void test9()
{
SingleLazy*p=SingleLazy::Creat();
SingleLazy::Destroy(); 但是还有个缺点,就是释放还是要手动释放,
特别是当有十几二十个单例对象的时候,一个个Destroy会麻烦
能不能智能一点呢
}
噢,智能指针,用智能指针,但是智能指针一般是程序结束后自动调用析构的,我们显示调用也不是不可以,但是智能指针的析构函数就得把指针置空,不然程序结束还会再调用,就二次析构了,我感觉用智能指针应该是可以的。还有第二种办法就是再写一个静态对象(不能是普通,不然会逻辑死循环),当程序结束,这个静态成员也就要调用析构函数释放了,我们在函数内把单例对象的释放方法放入,也就实现了智能化的释放资源。
class Garbage Garbage是SingleLazy的内部类,方便访问sgl变量
{
public:
~Garbage()
{
if(SingleLazy::sgl)
delete SingleLazy::sgl;
}
};
private:
static Garbage gb; gb是SingleLazy成员变量,也可以写到全局,这个时候是不是静态就无所谓了
说完懒汉模式后,我再提一种自己的,根据饿汉模式优化的。而且还不用操心释放的问题。
class SingleHungry
{
public:
static SingleHungry& Creat()
{
static SingleHungry sgl;
return sgl;
}
private:
~SingleHungry()
{
cout << "~SingleHungry()" << endl;
}
SingleHungry()
{
cout << "SingleHungry()" << endl;
}
SingleHungry(const SingleHungry& sh) = delete;
SingleHungry operator=(const SingleHungry& sh) = delete;
};
设计一个类不能被继承,最后的方法就是在该类名后加个final,这是c++11支持的。
而c++98则是将该类构造函数设为私有,这样派生类就调不到基类的构造函数,而且你会发现这个时候派生类的默认构造函数被delete了,感觉可能是编译器发现自己的默认构造初始化不了自己的成员,就气急败坏地删了,还有更多情况会被delete,有兴趣可了解了解。