> 文章列表 > 特殊类的实现

特殊类的实现

特殊类的实现

实现有特殊要求的类

1 不能被拷贝的类

C++98:

  1. 将拷贝构造函数与赋值运算符重载只声明不定义(防止类外调用);
  2. 将其访问权限设置为私有即可(防止成员函数内部拷贝)。
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. 将类的构造函数声明和实现为私有;
  2. 类的拷贝构造声明成私有,不实现;
  3. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建。
//方法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

  1. 将类的构造函数声明和实现为私有;
  2. 提供一个静态的成员函数,在该静态成员函数中完成栈对象的创建。
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:

  1. 将类的构造函数声明和实现为私有;派生类中调不到基类的构造函数,则无法继承
class NonInherit
{
public:static NonInherit GetInstance() { return NonInherit(); }
private:NonInherit() {}
};

C++11:

  1. 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;
}