文章目录
- 一、设计一个不能被拷贝的类
- 二、设计一个只能在堆上创建的类
- 三、设计一个只能在栈上创建的类
- 四、设计一个不能被继承的类
- 五、单例模式
- 1.懒汉模式
- 2.饿汉模式
一、设计一个不能被拷贝的类
拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
C++98:
将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。
class CopyBan
{
// ...
private:
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
//...
};
原因:
- 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就不能禁止拷贝了
- 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
C++11:
C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。
class CopyBan
{
// ...
CopyBan(const CopyBan&)=delete;
CopyBan& operator=(const CopyBan&)=delete;
//...
};
二、设计一个只能在堆上创建的类
方式一:
- 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
- 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建。
class HeapOnly {
public:
static HeapOnly* CreatObject() { return new HeapOnly(); }
private:
HeapOnly() {}
HeapOnly(const HeapOnly&) = delete;
};
这种方式需要提供创建对象的接口,那么有没有其它方式呢?
方式二:
- 将析构函数设置为私有。
- 另外生成一个public权限函数来,释放对象。
class HeapOnly {
public:
HeapOnly() {}
private:
~HeapOnly() {}
};
原因:C++是静态绑定语言,编译器管理栈上对象的生命周期,编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性。若析构函数不可访问,则不能在栈上创建对象。
三、设计一个只能在栈上创建的类
方法:将 new 和 delete 重载为私有。
在堆上生成对象,使用new关键词操作,其过程分为两阶段:第一阶段,使用new在堆上寻找可用内存,分配给对象;第二阶段,调用构造函数生成对象。
将new操作设置为私有,那么第一阶段就无法完成,就不能够再堆上生成对象。
class StackOnly {
private:
int _a;
public:
void* operator new(size_t size) = delete;
void operator delete(void* p) = delete;
StackOnly() :_a(0) {}
};
四、设计一个不能被继承的类
C++98:构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class NonInherit
{
public:
static NonInherit GetInstance(){ return NonInherit();}
private:
NonInherit() {}
};
C++11:final关键字,final修饰类,表示该类不能被继承
class A final
{
// ....
};
五、单例模式
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个
访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置
信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再
通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
1.懒汉模式
如果单例对象构造十分耗时或者占用很多资源,比如加载插件,初始化网络连接,读取文件等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
懒汉模式version1 -> 问题1:线程安全 问题2:内存泄露
class Singleton {
private:
static Singleton* _instance;
Singleton() { cout << "构造函数" << endl; }
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static Singleton* getInstance()
{
if (_instance == nullptr)
_instance = new Singleton();
return _instance;
}
~Singleton() { cout << "析构函数" << endl; }
};
Singleton* Singleton::_instance = nullptr;
运行上面的代码我们发现资源没有释放:
针对资源释放的问题,我们可以通过实现一个内嵌的垃圾回收类,定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象:
class Singleton {
public:
static Singleton* getInstance()
{
if (_instance == nullptr)
_instance = new Singleton();
return _instance;
}
~Singleton() { cout << "析构函数" << endl; }
public:
// 实现一个内嵌垃圾回收类
class CGarbo {
public:
~CGarbo() {
if (Singleton::_instance != nullptr) { delete Singleton::_instance; }
}
};
// 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象
static CGarbo Garbo;
private:
static Singleton* _instance;
Singleton() { cout << "构造函数" << endl; }
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton&) = delete;
};
Singleton* Singleton::_instance = nullptr;
Singleton::CGarbo Garbo;
懒汉模式version2 -> 不足:要求使用智能指针,锁有开销
我们不仅可以用内部类的思想来解决资源泄露的问题,还可以使用智能指针:
class Singleton {
public:
typedef shared_ptr<Singleton> Ptr;
static Ptr getInstance()
{
if (_instance == nullptr)
{
std::lock_guard<std::mutex> _lck(_mtx);
if (_instance == nullptr)
_instance = shared_ptr<Singleton>(new Singleton());
}
return _instance;
}
~Singleton() { cout << "析构函数" << endl; }
private:
static Ptr _instance;
static std::mutex _mtx;
private:
Singleton() { cout << "构造函数" << endl; }
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton&) = delete;
};
Singleton::Ptr Singleton::_instance = nullptr;
std::mutex Singleton::_mtx;
shared_ptr
和mutex
都是C++11的标准,以上这种方法的优点是:
- 基于 shared_ptr, 用了C++比较倡导的 RAII思想,用对象管理资源,当 shared_ptr 析构的时候,new 出来的对象也会被 delete掉。以此避免内存泄漏。
- 加了锁,使用互斥量来达到线程安全。这里使用了两个 if 判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,避免每次调用 get_instance的方法都加锁,提高效率。
不足之处在于:
- 使用智能指针会要求用户也得使用智能指针,非必要不应该提出这种约束; 使用锁也有开销; 同时代码量也增多了,实现上我们希望越简单越好。
- 还有更加严重的问题,在某些平台(与编译器和指令集架构有关),双检锁会失效!
懒汉模式version3 -> Meyers’s 的单例
C++11 规定了 local static 在多线程条件下的初始化行为,要求编译器保证了内部静态变量的线程安全性。在 C++11 标准下,《 Effective C++》提出了一种更优雅的单例模式实现,使用函数内的 local static 对象。这样,只有当第一次访问getInstance()
方法时才创建实例。这种方法也被称为Meyers' Singleton
。
class Singleton {
private:
Singleton() { cout << "构造函数" << endl; }
~Singleton() { cout << "析构函数" << endl; }
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static Singleton& getInstance()
{
static Singleton _instance;
return _instance;
}
};
2.饿汉模式
单例实例在程序运行时被立即执行初始化,如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。:
class Singleton
{
private:
static Singleton _instance;
private:
Singleton() { cout << "构造函数" << endl; }
~Singleton() { cout << "析构函数" << endl; }
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static Singleton& getInstance() { return _instance; }
};
Singleton Singleton::_instance;