设计一个类,只能在堆上创建对象
实现方法
- 将构造函数私有化,只给用户提供一个获取对象的接口,接口内部是在堆上创建对象;
- 因为用户不能自己创建对象,所以就无法调用我们的接口来获取对象,所以这个接口必须被设计为静态接口,这样用户可以通过类名来调用接口获取对象;
- 因为拷贝构造出来的对象是在栈上,所以我们应该将拷贝构造函数禁用,这里有两种方法禁用拷贝构造:
- 将拷贝构造函数声明为私有,且不实现,有个缺点:这样做不能杜绝在类内部定义拷贝构造函数;
- 将拷贝构造函数置为
delete
,完全杜绝;
class HeapOnly{
public:
static HeapOnly* CreateObject(){
return new HeapOnly;
}
private:
HeapOnly() {}
HeapOnly(const HeapOnly&);
HeapOnly(const HeapOnly&) = delete;
};
设计一个类,只能在栈上创建对象
实现方法一
- 将构造函数私有化,只给用户提供一个获取对象的接口,接口内部是在栈上创建对象;
- 因为用户不能自己创建对象,所以就无法调用我们的接口来获取对象,所以这个接口必须被设计为静态接口,这样用户可以通过类名来调用接口获取对象;
class StackOnly{
public:
static StackOnly CreateObject(){
return StackOnly();
}
private:
StackOnly() {}
};
实现方法二
- 屏蔽
new
的功能就可以防止用户在堆上创建对象,在前面学习new
的时候说过,new
在底层调用的是void* operator new(size_t size)
函数,因此我们将operator new()
函数屏蔽掉,这样就可以屏蔽new
了,当然也要防止定位new
,但其实屏蔽了operator new()
,实际也将定位new
屏蔽掉; new
和delete
是用户进行动态内存申请和释放的操作符,operator new
和operator delete
是系统提供的全局函数,new
在底层调用operator new
全局函数来申请空间,delete
在底层通过operator delete
全局函数来释放空间;- 使用这种方法有一个 bug,那就是全局的对象可以被创建在堆上;
class StackOnly{
public:
StackOnly() {}
private:
void* operator new(size_t size);
void operator delete(void* p);
void* operator new(size_t size) = delete;
void operator delete(void* p) = delete;
};
设计一个类,不能被拷贝
实现方法
- 将拷贝构造函数与赋值运算符函数设置为私有,只声明不定义;或者将拷贝构造函数与赋值运算符函数置为
delete
;
class CopyBan{
private:
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
CopyBan(const CopyBan&)=delete;
CopyBan& operator=(const CopyBan&)=delete;
};
设计一个类,不能被继承
实现方法
- C++98 中,可以将一个类的构造函数私有化,派生类中调用不到基类的构造函数,则无法继承;
- C++11 中,可以使用
final
关键字,表明这是一个最终类,不可被继承;
class NonInherit{
public:
static NonInherit GetInstance(){
return NonInherit();
}
private:
NonInherit()
{}
};
class A final
{
};
设计一个类,只能创建一个对象
概念
- 一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享;比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理;
饿汉模式
- 不管用不用这个类的对象,在程序启动时都会为该类实例化一个对象,如何实现呢?在类中定义一个该类的静态对象成员变量,因为静态成员在程序运行时就会确定并分配空间,且只有一个;
- 优点:简单;
- 缺点:因为在一开始要加载所有数据,可能会导致进程启动慢,如果有多个单例类对象实例启动,则顺序不确定;
class Singleton{
public:
static Singleton* GetInstance(){
return &m_instance;
}
private:
Singleton(){};
Singleton(Singleton const&);
Singleton& operator=(Singleton const&);
Singleton(Singleton const&) = delete;
Singleton& operator=(Singleton const&) = delete;
static Singleton m_instance;
};
Singleton Singleton::m_instance;
懒汉模式
- 饿汉模式在一开始就将所有东西都加载了,保证了后续的效率,但是有些时候有些数据根本用不到,所以出现了懒汉模式,使用的时候再创建对象,也叫延迟加载;
- 优点:第一次使用实例对象时,才会创建对象;进程启动时无负载,启动快;多个单例实例启动顺序自由控制;
- 缺点:复杂;
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
class Singleton{
public:
static Singleton* GetInstance() {
if (nullptr == m_pInstance) {
m_mtx.lock();
if (nullptr == m_pInstance) {
m_pInstance = new Singleton();
}
m_mtx.unlock();
}
return m_pInstance;
}
class CGarbo {
public:
~CGarbo(){
if (Singleton::m_pInstance)
delete Singleton::m_pInstance;
}
};
static CGarbo Garbo;
private:
Singleton(){};
Singleton(Singleton const&);
Singleton& operator=(Singleton const&);
static Singleton* m_pInstance;
static mutex m_mtx;
};
Singleton* Singleton::m_pInstance = nullptr;
Singleton::CGarbo Garbo;
mutex Singleton::m_mtx;