C++特殊类设计
- 1、请设计一个类,不能被拷贝
- 2、请设计一个类,只能在堆上创建对象
- 3、请设计一个类,只能在栈上创建对象
- 4、请设计一个类,不能被继承
- 5、请设计一个类,只能创建一个对象(单例模式)
- 5.1、饿汉模式
- 5.2、懒汉模式
1、请设计一个类,不能被拷贝
拷贝只会发生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
C++98方式:将拷贝构造和赋值私有
class CopyBan
{
public:CopyBan() {}
private:CopyBan(const CopyBan& cb);CopyBan& operator=(const CopyBan& cb);
};
1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了
2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
C++11方式:扩展delete用法
class CopyBan
{
public:CopyBan() {}CopyBan(const CopyBan& cb) = delete;CopyBan& operator=(const CopyBan& cb) = delete;
};
C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。
2、请设计一个类,只能在堆上创建对象
方式一:析构函数私有
class HeapOnly
{
public:void Destroy() {delete this;}
private:~HeapOnly(){}
};
HeapOnly* hp = new HeapOnly;
hp->Destroy();
在栈上创建的对象除了作用域会自动调用析构函数,析构函数私有就无法调用了,所以无法在栈上创建对象。
可以在堆上创建对象,但是有个问题,delete的时候无法调用析构函数,因为delete的原理是析构函数+释放空间,所以我们提供一个Destroy接口,直接在函数内部delete,这样就可以调用析构函数。
方式二:构造函数私有
class HeapOnly
{
public:static HeapOnly* CreateObj(){return new HeapOnly;}
private:HeapOnly(){}HeapOnly(const HeapOnly& hp) = delete;HeapOnly& operator=(const HeapOnly& hp) = delete;
};
HeapOnly* hp = HeapOnly::CreateObj();
构造函数私有后,既不能在栈上创建,也不能在堆上创建。因此我们需要提供一个CreateObj函数,在里面创建然后将指针返回给外部接收,并且这个函数必须是静态的,否则调不到。
但是这样还是防不住下面这种方式:
HeapOnly* hp = HeapOnly::CreateObj();
HeapOnly copy(*hp);
所以还需要防拷贝。
3、请设计一个类,只能在栈上创建对象
方式:构造函数私有
class StackOnly
{
public:static StackOnly CreateObj(){StackOnly st;return st;}
private:StackOnly(){}void* operator new(size_t size) = delete;
};
StackOnly st1 = StackOnly::CreateObj();
构造函数私有,然后提供一个静态成员函数,在该成员函数里面创建对象返回。
但是还是防不住下面这种情况:
StackOnly* hp = new StackOnly(st1);
new的原理就是operator new加构造函数,所以我们需要禁掉operator new。
4、请设计一个类,不能被继承
C++98方式:构造函数私有
class NonInherit
{
public:static NonInherit GetInstance(){return NonInherit();}
private:NonInherit(){}
};
C++11方式:final修饰
class A final
{
// ...
};
5、请设计一个类,只能创建一个对象(单例模式)
单例模式:
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
单例模式有两种实现模式:饿汉模式和懒汉模式。
5.1、饿汉模式
之前的CreateObj,不管是new还是栈上创建再返回,都是不同的对象,现在需要的是同一个对象,思考一下该怎么做?
namespace hungry
{class Singleton{public:static Singleton& GetInstance(){return _sinst;}void Add(const pair<string, string>& kv){_dict[kv.first] = kv.second;}void Print(){for (const auto& e : _dict){cout << e.first << ":" << e.second << endl;}cout << endl;}private:Singleton(){}Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& s) = delete;map<string, string> _dict;// ...static Singleton _sinst;};Singleton Singleton::_sinst;
}int main()
{cout << &hungry::Singleton::GetInstance() << endl;cout << &hungry::Singleton::GetInstance() << endl;cout << &hungry::Singleton::GetInstance() << endl;hungry::Singleton::GetInstance().Add({ "xxx", "111" });hungry::Singleton::GetInstance().Add({ "yyy", "222" });hungry::Singleton::GetInstance().Add({ "zzz", "333" });hungry::Singleton::GetInstance().Print();return 0;
}
实现:
1、首先将构造函数私有。
2、声明静态类对象,然后在类外定义,提供一个获取对象的静态成员函数GetInstance()。
3、防拷贝,将拷贝和赋值声明为删除。
饿汉模式:就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。
但是也有下面的问题:
1、如果单例对象初始化内容很多,影响启动速度。
2、如果有两个单例类,相互依赖关系。假设有A、B两个单例类,要求先创建A,然后再创建B,B的初始化依赖A。
5.2、懒汉模式
namespace lazy
{class Singleton{public:static Singleton& GetInstance(){if (_psinst == nullptr){_psinst = new Singleton;}return *_psinst;}static void DelInstance(){if (_psinst){delete _psinst;_psinst = nullptr;}}void Add(const pair<string, string>& kv){_dict[kv.first] = kv.second;}void Print(){for (const auto& e : _dict){cout << e.first << ":" << e.second << endl;}cout << endl;}private:Singleton(){}Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& s) = delete;~Singleton(){cout << "~Singleton()" << endl;// map数据写到文件中FILE* fin = fopen("map.txt", "w");for (auto& e : _dict){fputs(e.first.c_str(), fin);fputs(":", fin);fputs(e.second.c_str(), fin);fputs("\n", fin);}}map<string, string> _dict;// ...static Singleton* _psinst;};Singleton* Singleton::_psinst = nullptr;
}
懒汉模式:第一次调用GetInstance的时候创建单例对象
那么如何释放呢?单例一般不用释放,进程结束直接带走了。
特殊场景:1、中途需要显示释放 2、程序结束时,需要做一些特殊动作(如持久化)
上面的析构函数就是场景2,假设需要把数据写入文件,那么如果是进程结束是不会走析构函数的,无法做到数据持久化。
所以可以提供一个DelIstance函数,中途可以显示释放:
cout << &lazy::Singleton::GetInstance() << endl;
cout << &lazy::Singleton::GetInstance() << endl;
cout << &lazy::Singleton::GetInstance() << endl;
lazy::Singleton::GetInstance().Add({ "xxx", "111" });
lazy::Singleton::GetInstance().Add({ "yyy", "222" });
lazy::Singleton::GetInstance().Add({ "zzz", "333" });
lazy::Singleton::GetInstance().Print();
lazy::Singleton::GetInstance().DelInstance();
那么如果我不想显示释放,并且还希望数据持久化呢?
可以写这么一个类:
class GC
{
public:~GC(){lazy::Singleton::GetInstance().DelInstance();}
};GC gc;
还可以直接写在Singleton内部:
namespace lazy
{class Singleton{public:static Singleton& GetInstance(){if (_psinst == nullptr){_psinst = new Singleton;}return *_psinst;}static void DelInstance(){if (_psinst){delete _psinst;_psinst = nullptr;}}void Add(const pair<string, string>& kv){_dict[kv.first] = kv.second;}void Print(){for (const auto& e : _dict){cout << e.first << ":" << e.second << endl;}cout << endl;}class GC{public:~GC(){lazy::Singleton::GetInstance().DelInstance();}};private:Singleton(){}Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& s) = delete;~Singleton(){cout << "~Singleton()" << endl;// map数据写到文件中FILE* fin = fopen("map.txt", "w");for (auto& e : _dict){fputs(e.first.c_str(), fin);fputs(":", fin);fputs(e.second.c_str(), fin);fputs("\n", fin);}}map<string, string> _dict;// ...static Singleton* _psinst;static GC _gc;};Singleton* Singleton::_psinst = nullptr;Singleton::GC Singleton::_gc;
}int main()
{cout << &lazy::Singleton::GetInstance() << endl;cout << &lazy::Singleton::GetInstance() << endl;cout << &lazy::Singleton::GetInstance() << endl;lazy::Singleton::GetInstance().Add({ "xxx", "111" });lazy::Singleton::GetInstance().Add({ "yyy", "222" });lazy::Singleton::GetInstance().Add({ "zzz", "333" });lazy::Singleton::GetInstance().Print();return 0;
}