C++的特殊类
一、单例模式
单例模式,是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点来获取该实例。要确保只有一个实例,关键就是要ban掉构造函数以及拷贝构造和赋值拷贝,防止出现更多实例。
在ban掉构造函数以后,要如何创建出唯一的一个实例呢?只能从静态变量这个角度来想办法解决。从单例模式的应用场景入手,单例模式通常用来统一写入日志文件、对共享设备统一管理等。因此,有饿汉模式和懒汉模式两种角度的实现方法:
1.1 饿汉模式
饿汉模式,就是在程序启动时就完成单例的实例化,因此,无论是否会用到这个单例,只要使用了饿汉模式,程序启动,这个单例就会占用空间。但是,优势就是调用快速,实现简单:
class A {
public:static A& getA() {return a;}void Insert(pair<string, string> p) {mss.insert(p);}void Print() {for (auto& e : mss) {cout << e.first << ":" << e.second << endl;}}A& operator=(const A& a) = delete;A(const A& a) = delete;
private:A(){ }map<string, string> mss;static A a;
};A A::a;int main(void) {A::getA().Insert(make_pair("apple", "苹果"));A::getA().Insert(make_pair("banana", "香蕉"));A::getA().Insert(make_pair("cat", "猫"));A::getA().Insert(make_pair("delete", "删除"));A::getA().Print();return 0;
}
通过在main之前实例化静态成员变量a,再借助调用函数就可以实现单例模式的饿汉模式,全局仅有一个A的实例,因此所有的插入都是在同一个map中进行修改。
1.2 懒汉模式
懒汉模式,则是在需要调用时才会实例化,相比饿汉模式,能控制各个单例初始化的顺序,并且由于是调用时才会实例化,没有用到的类就不会被实例化出来占用空间,且可以节省在调用main函数之前的准备时间。缺点就是第一次调用时需要时间实例化,实现更为复杂:
class B {
public:static B* getB() {if (!pb) {pb = new B;}return pb;}void Add(const string& key, const string& value){m[key] = value;}void Print(){for (auto& kv : m){cout << kv.first << ":" << kv.second << endl;}cout << endl;}private:B(){ }~B() {cout << "把数据写入硬盘" << endl;}B(const B&) = delete;B& operator=(const B&) = delete;map<string, string> m;class gc {public:~gc() {delete pb;pb = nullptr;cout << "~B" << endl;}};static gc _gc;static B* pb;
};B* B::pb = nullptr;
B::gc B::_gc;int main(void) {B::getB()->Add("apple", "苹果");B::getB()->Add("banana", "香蕉");B::getB()->Add("cat", "猫");B::getB()->Add("delete", "删除");B::getB()->Print();return 0;
}
这里用指针的形式实现懒汉模式,其实还有另一种采用静态局部变量的形式,也就是将饿汉模式的静态成员变量,放到get函数,在调用时如果没有实例化过则会实例化,如果实例化了就不会再实例化了,这里不做过多介绍。
然后,指针的销毁通过智能指针的思想进行解决,也可以直接使用指针指针进行管理,当程序结束时,就会调用析构函数将指针销毁。这里是使用了一个静态类完成对静态指针的管理。
二、一些其他类型类的实现
2.1 不能被继承的类
在C++98中,可以通过将父类的构造函数私有化来实现,而在C++11中则可以通过使用final关键字实现不能被继承。
2.2 不能被拷贝的类
在C++98中,可以将拷贝构造和赋值拷贝声明在private下,这样既不能被调用,又不能在类外重写。而在C++11中,则可以简单的使用delete删除掉这两个函数,从而实现不能被拷贝。
2.3 只能在堆上创建的类
同样也要通过限制构造函数来达成目的,再通过特定的可供调用的函数来完成构造。然后再删除掉拷贝构造和赋值拷贝,限制其他构造的手段:
class A {
public:static A* Initial(int a) {return new A(a);}
private:A(int a):_a(a){}A(const A&) = delete;A& operator=(const A&) = delete;int _a;
};
这样就只能通过调用A::Initial函数完成构造 ,而这样构造出的类一定是堆上的类。
2.4 只能在栈上创建的类
还是通过限制构造函数,但是,禁止掉拷贝构造函数会导致调用的构造函数无法传回构造出的类。因此,直接禁止掉new:
class A {
public:static A Initial(int a) {A tmp = A(a);return tmp;}A(A&&) = default;A& operator=(A&&) = default;
private:A(int a):_a(a){}void* operator new(size_t) = delete;A(const A&) = delete;A& operator=(const A&) = delete;int _a;
};
这样,要在堆上创建就只能调用new但是new又被删除了,因此,只能在栈上创建。