特殊类的实现
实现有特殊要求的类
1 不能被拷贝的类
C++98:
- 将拷贝构造函数与赋值运算符重载只声明不定义(防止类外调用);
- 将其访问权限设置为私有即可(防止成员函数内部拷贝)。
class CopyBan
{// ...private:CopyBan(const CopyBan&);CopyBan& operator=(const CopyBan&);//...
};
C++11:在默认成员函数后跟上=delete
,可以让编译器删除掉该默认成员函数,在编译的时候就报错。
class CopyBan
{// ...CopyBan(const CopyBan&)=delete;CopyBan& operator=(const CopyBan&)=delete;//...
};
2 只能在堆上创建对象的类
方法1
- 将类的构造函数声明和实现为私有;
- 类的拷贝构造声明成私有,不实现;
- 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建。
//方法1
class HeapOnly
{
public: static HeapOnly* CreateObject() { return new HeapOnly; }
private: HeapOnly() {}//拷贝构造函数的写法两者选其一HeapOnly(const HeapOnly&);//c98HeapOnly(const HeapOnly&) = delete;//c11
};
//调用
HeapOnly* ho1 = HeapOnly::CreateObject();//可以
HeapOnly ho2;//err "HeapOnly::HeapOnly()" (已声明 所在行数:14) 不可访问
HeapOnly ho3(*ho);//err C98: "HeapOnly::HeapOnly(const HeapOnly &)" (已声明 所在行数:16) 不可访问 || C11: “HeapOnly::HeapOnly(const HeapOnly &)”: 尝试引用已删除的函数
方法2 把析构函数声明和实现为私有,把拷贝构造函数声明为私有,不实现。提供一个public的销毁对象方法。
class HeapOnly
{
public:HeapOnly() {}void destory(){this->~HeapOnly();}
private:~HeapOnly(){}//拷贝构造函数的写法两者选其一HeapOnly(const HeapOnly&);//c98//HeapOnly(const HeapOnly&) = delete;//c11
};
//调用
HeapOnly* ho1 = new HeapOnly;//可以
HeapOnly ho;//err 无法访问构造函数
3 只能在栈上创建对象的类
方法1
- 将类的构造函数声明和实现为私有;
- 提供一个静态的成员函数,在该静态成员函数中完成栈对象的创建。
class StackOnly
{
public:static StackOnly create(){return StackOnly();}
private:StackOnly(){}
};
//调用
StackOnly s1 = StackOnly::create();//可以
static StackOnly s2;//err "StackOnly::StackOnly()" (已声明 所在行数:13) 不可访问
static StackOnly s3 = StackOnly::create();//这个没办法封死
StackOnly s4;//全局变量err 无法访问构造函数
StackOnly s5 = new StackOnly;//err 无法访问构造函数
如果仅仅是重载operator new和operator delete,都加上delete的话,只能禁掉在堆上创建对象,无法禁掉静态区、全局区等变量的创建。
4 不能被继承的类
C++98:
- 将类的构造函数声明和实现为私有;派生类中调不到基类的构造函数,则无法继承
class NonInherit
{
public:static NonInherit GetInstance() { return NonInherit(); }
private:NonInherit() {}
};
C++11:
- final关键字,final修饰类,表示该类不能被继承。
class A final
{// ....
};
5 只能创建一个对象的类-单例模式
设计模式:迭代器模式、适配器模式(stack、queue)、单例模式、工厂模式、观察者模式等。
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
思路:一个类只能创建一个对象,那就是要把构造函数、拷贝构造、赋值构造函数都私有。
饿汉模式
程序启动时就创建一个唯一的实例对象。如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。
优点:简单,无线程安全问题。
缺点:类内static数据对象初始化是在main函数之前!如果这个对象初始化时数据过多可能会导致进程启动慢;多个单例类有初始化依赖关系,饿汉模式无法控制,因为多个单例类对象实例启动顺序是不确定的。(比如A和B都是单例类,由于B依赖A,所以要求先初始化A再初始化B,饿汉模式就无法达到目的)
class Singleton
{
public:static Singleton* getSingleton(){return &_instance;}
private:// 构造函数私有Singleton() {};Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& s) = delete;static Singleton _instance;
};
Singleton Singleton::_instance;//static变量要在类外初始化
int main()
{Singleton* s = Singleton::getSingleton();return 0;
}
懒汉模式
如果单例对象构造十分耗时或者占用很多资源,如加载插件、 初始化网络连接、读取
文件等,即使该对象在程序运行时不会用到,饿汉模式下该对象也要在程序一开始就进行初始化,即加载缓慢又浪费资源。
所以这种情况使用懒汉模式(延迟加载)更好。
优点:第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控
制。
缺点:复杂,有线程安全问题需要加锁,一般单例对象不需要考虑资源释放(若有需要,还需要添加资源回收的功能),还要考虑new异常导致的无法解锁问题(要么用try catch,要么用luckguard)
template<class Lock>
class LockGuard{
public:LockGuard(Lock& lk) : _lk(lk){ _lk.lock(); }~LockGuard(){ _lk.unlock(); }
private:Lock& _lk;//因为锁是不允许拷贝构造的,所以这里要用引用
};class Singleton
{
public:static Singleton* getSingleton(){//因为单例模式只在第一次调用的时候创建对象,加锁解锁的动作只需要在第一次有,用双检查加锁来实现if(_pinstance == nullptr){//第1次判断是避免每次都加锁,提高性能_mtx.lock();//第2次判断是保证线程安全且只new一次 第一次获取单例对象时创建对象和加锁try{if(_pinstance == nullptr) _pinstance = new Singleton;}catch(...){_mtx.unlock();throw;}_mtx.unlock();}return _pinstance;}// 实现一个内嵌垃圾回收类 class CGarbo {public:~CGarbo(){if (Singleton::_pinstance) delete Singleton::_pinstance;}};// 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象static CGarbo Garbo;// 构造函数私有Singleton() {};Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& s) = delete;static Singleton* _pinstance;//唯二的变化 单例对象指针static mutex _mtx;//唯二的变化 互斥锁
};
Singleton* Singleton::_pinstance = nullptr;//static变量要在类外初始化
Singleton::CGarbo Singleton::CGarbo CGarbo;
mutex Singleton::_mtx;
int main()
{Singleton* s = Singleton::getSingleton();return 0;
}
全局静态变量与局部静态变量的初始化
全局静态变量的构造函数会在main之前执行,析构函数在main之后执行。
而局部静态变量是在main之后初始化
//该写法是懒汉模式,静态的局部变量在main函数之后被调用才创建初始化的
//c11前对静态局部变量的初始化没有做出相关规定,故无法保证线程安全,c11后作出规定了编译器实现了保证静态局部变量的线程安全
static Singleton& getSingleton()
{static Singleton inst;//函数体内定义该对象,只会在调用的时候初始化,并且在main函数之后创建return inst;
}