目录
引言
1.请设计一个类,不能被拷贝
2. 请设计一个类,只能在堆上创建对象
为什么设置实例的方法为静态成员呢
3. 请设计一个类,只能在栈上创建对象
4. 请设计一个类,不能被继承
5. 请设计一个类,只能创建一个对象(单例模式)
饿汉模式
懒汉模式
单例对象一般不考虑析构
为什么私有析构呢?
引言
在当今的软件开发实践中,特殊类设计模式扮演着至关重要的角色,它们不仅提高了代码的可维护性和可扩展性,还确保了系统的稳定性和性能。其中,单例模式作为最常用的设计模式之一,以其独特的实例管理方式,成为了许多场景下的首选解决方案。本文旨在探讨C++中单例模式及其他特殊类设计的精髓,通过分析其背后的设计哲学和实现技巧,帮助读者掌握这些高级编程技巧,以提升其在软件开发过程中的设计能力和代码质量。
在我们设计一个特殊类的时候,一般要把构造、拷贝和赋值私有保护,防止被其他类拷贝或者产生不想要(意料之外)的错误。
1.请设计一个类,不能被拷贝
拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,
class CopyBan
{// ...private:CopyBan(const CopyBan&);CopyBan& operator=(const CopyBan&);//...
};
C++11
class CopyBan
{// ...CopyBan(const CopyBan&)=delete;CopyBan& operator=(const CopyBan&)=delete;//...
};
同时我们还可以设置一个基类,让子类继承基类,让子类正常实现功能,设置基类不可被拷贝即可。
2. 请设计一个类,只能在堆上创建对象
class HeapOnly
{
public: static HeapOnly* CreateObject() { return new HeapOnly; }
private: HeapOnly() {}// C++98// 1.只声明,不实现。因为实现可能会很麻烦,而你本身不需要// 2.声明成私有HeapOnly(const HeapOnly&);// or// C++11 HeapOnly(const HeapOnly&) = delete;
}
由于没有this指针,无法调用构造,而是只是去new这个镀锡,由系统去完成资源的申请
静态不能调用非静态就是因为没有this指针,缺少调用的参数
但是new和delete这种操作不需要this指针
那每次调的时候,返回的都是一个新的静态对象吗?--- NO!这个方法是静态的,但是返回的变量不是静态的
为什么设置实例的方法为静态成员呢
收线确定的是,一定是内部new创建的对象。
因为如果设置为非静态成员,那就只能通过对象去调用,但是我们不能去在栈区创建对象。
并且构造私有,外部一定不可以创建对象,只能用类调用,所以必须静态!
为什么禁用拷贝构造:比如A a1(a2);这样a1依然是在栈区!
3. 请设计一个类,只能在栈上创建对象
只能通过类型创建,禁止new对象
class StackOnly
{
public:static StackOnly CreateObj(){StackOnly obj;return obj;}void* operator new(size_t size) = delete;void operator delete(void* p) = delete;
private:StackOnly() :_a(0){}
private:int _a;
};
1.设置为静态:
并且构造私有,外部一定不可以创建对象,只能用类调用,所以必须静态!
2.为什么不可以禁用拷贝构造:比如A a1(a2);这样a1依然是在栈区!符合要求,并且匿名对象的声明周期只在这一行!为了防止匿名对象周期太短,无法进行较好的连续性开发,所以需要赋值给另一个栈区的对象。
3.可以返回临时对象,只需要赋值给其他对象即可(栈区);也可以返回非匿名对象,也是采用赋值的方式进行连续性开发。
4.为了防止创建堆区对象,需要禁用new和delete(为了防止创建栈区对象时,需要禁用拷贝、赋值)
4. 请设计一个类,不能被继承
C++98方式
// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class NonInherit
{
public:static NonInherit GetInstance(){return NonInherit();}
private:NonInherit(){}
};
class A final
{// ....
};
5. 请设计一个类,只能创建一个对象(单例模式)
饿汉模式
饿汉:设置为全局,提前创建。
既然是单例,一定先把构造私有。
在类的外部虽然不能创建全局的对象,但是在类的内部可以创建。----思路:类的内部声明静态成员,类的外部定义,这样就是一个全局的数据。
// 懒汉模式:第一次用的时候再创建(现吃现做)
class A {
public:static A* GetInstance() {return _inst;}
private:A() {}map<string, string> _dict;int _n = 0;static A* _inst;
};
我们在类的内部创建对象的时候,当然不能
class A
{
A _a;
}
访问成员需要借助对象,这时候就需要调用GetInstance函数
优点:创建简单
缺点:1.进程启动慢
2.一旦存在两个全局单例,不能控制单例启动的先后顺序。
懒汉模式
现吃现做,不着急,main函数内创建。
只需要把静态的对象换成指针即可,获取实例的时候,如果指针是nullptr,那么创建,如果不是,那么直接返回
线程不安全,两个线程同时进来,可能会new两个对象---加锁
单例对象一般不考虑析构
恶汉不存在释放的问题--全局,
懒汉对象也一般不需要释放---单例对象一般是生命周期伴随整个程序,对整个程序都起到至关重要的作用,进程结束时,会自动释放。
就算要释放,如果我们期望既可以手动释放(析构不能手动释放),也可以在main函数结束时自动释放
class B
{
public:static B* GetInstance(){if (_inst == nullptr){_inst = new B;}return _inst;}static void DelInstance(){if (_inst){delete _inst;_inst = nullptr;}}private:B(){}~B(){// 持久化:要求把数据写到文件cout << "数据写到文件" << endl;}B(const B& aa) = delete;B& operator=(const B& aa) = delete;map<string, string> _dict;int _n = 0;static B* _inst;class gc{public:~gc(){DelInstance();}};static gc _gc;
};B* B::_inst = nullptr;
B::gc B::_gc;
可以私有析构函数
提供一个调用析构函数的接口DelInstance,我们可以手动调用这个接口去进行析构。
我们新建一个内部类,这个内部类是外部类的友元,可以访问私有。等main函数结束的时候_gc调用析构,析构会调用DelInstance。
这样可以做到:1.显示释放(析构不允许显示调用,但是delete指针的时候,可以调用析构)
2.没有显示释放,等程序结束也会释放
为什么私有析构呢?
B* instance = B::GetInstance();
delete instance;
B* instance2 = B::GetInstance();
如果是public的话,每个人可以自己delete这个,那么就违背单例模式的原则了!
而这块放的这个DelInstance函数是意思万一情况下,不使用单例了,自己想释放的话,可以通过这个函数释放,但是一般也不会这样做的,一般gc就替咱们回收了
总之就是:不能让其他人去随便delete单例,所以私有;同时希望gc能够管理单例,等程序结束,gc清理的时候,gc会协助清理单例