C++ :特殊类设计
特殊类设计
- (一)设计一个不能被拷贝的类
- 方法1 C++98
- 方法2 C++11
- (二) 设计一个不能被继承的类
- 方法1 C++98
- 方法2 C++11
- (三)设计一个只能在堆上创造对象的类
- (四) 设计一个只能在栈上创建对象的类
- (五)单例模式
- 饿汉模式
- 懒汉模式
- 释放单例对象
- 方法1
- 方法2
(一)设计一个不能被拷贝的类
拷贝只会放生在两个场景中:
- 拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
方法1 C++98
将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。
- 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了
- 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
class CopyBan
{
// ...
private:
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
//...
};
方法2 C++11
C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。
class CopyBan
{
// ...
CopyBan(const CopyBan&)=delete;
CopyBan& operator=(const CopyBan&)=delete;
//...
};
(二) 设计一个不能被继承的类
方法1 C++98
C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class NonInherit
{
public:
static NonInherit GetInstance()
{
return NonInherit();
}
private:
NonInherit()
{}
};
方法2 C++11
final关键字,final修饰类,表示该类不能被继承。
class NonInherit final
{
// ....
};
(三)设计一个只能在堆上创造对象的类
实现方法:
- 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
- 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
class HeapOnly
{
public:
static HeapOnly* CreateObject()
{
return new HeapOnly;
}
private:
HeapOnly() {}
//C++98 声明成私有,不实现。
HeapOnly(const HeapOnly&);
HeapOnly& operator=(const HeapOnly& h);
//C++11
HeapOnly(const HeapOnly&) = delete;
HeapOnly& operator= (const HeapOnly& h) = delete;
};
(四) 设计一个只能在栈上创建对象的类
实现方法:
- 将构造函数私有化,然后设计静态方法创建对象返回
class StackOnly
{
public:
static StackOnly CreateObject()
{
return StackOnly();
}
void* operator new(size_t size) = delete;
void operator delete(void* p) = delete;
private:
StackOnly()
{}
};
这里需要禁止operator new
和 operator delete
函数,但是不能禁止拷贝和赋值函数
new和 delete :
- new的实现操作首先调用operator new函数申请空间,第二步是在申请的空间当中执行构造函数,完成对象初始化的工作。
- delete的实现操作首先在对应的空间中执行析构函数,完成对象中资源的清理工作,第二步是调用operator delete函数释放对象空间。
- 因此我们需要禁止
operator new
和operator delete
函数
那么我们为什么不禁止拷贝和赋值函数呢?
因为我们创建对象的时候需要用到拷贝或者赋值函数
StackOnly hp = StackOnly::CreateObj();
StackOnly copy(hp)
可以看到,我们在栈上创建对象的时候是需要通过拷贝或者赋值来创建对象的。
(五)单例模式
什么是单例模式:
- 一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个
访问它的全局访问点,该实例被所有程序模块共享。 - 比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
饿汉模式
程序启动时就创建一个唯一的实例对象。
实现方法:
- 将构造函数设置为私有,并且将拷贝构造和赋值函数删除,防止外部创建对象。
- 在类的内部创建一个静态的单例对象,在程序入口之前完成单例对象的初始化。
- 提供一个访问这个全局对象的静态函数
class Hungry
{
public:
3.
static Hungry& GetInstance()
{
return _h;
}
private:
1.
Hungry()
{}
//禁止拷贝,赋值
Hungry(const Hungry& h) = delete;
Hungry& operator= (const Hungry& h) = delete;
2.
static Hungry _h;
};
Hungry Hungry::_h; // 在程序入口之前就完成单例对象的初始化
如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避
免资源竞争,提高响应速度更好。
但如果不是可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定。
懒汉模式
如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取
文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,
就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
实现方法:
- 将构造函数设置为私有,并且将拷贝构造和赋值函数删除,防止外部创建对象。
- 在类的内部创建一个静态的指向单例对象的指针,在程序入口之前初始化单例对象的指针为nullptr。
- 提供一个访问这个全局对象的静态函数
class Lazy
{
public:
3.
static Lazy& GetInstance()
{
if (_lazy == nullptr)
{
_lazy = new Lazy;
}
return *_lazy;
}
// 一般单例不用释放。
// 特殊场景:1、中途需要显示释放 2、程序结束时,需要做一些特殊动作(如持久化)
static void Delete()
{
if (_lazy)
{
delete _lazy;
_lazy = nullptr;
}
}
private:
1.
Lazy()
{}
//防拷贝,赋值
Lazy(const Lazy& lazy) = delete;
Lazy& operator= (const Lazy& lazy) = delete;
2.
static Lazy* _lazy;
};
Lazy* Lazy::_lazy = nullptr;
优点:
- 第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控
制。
缺点:
- 复杂
这里存在线程安全问题但是我还没学到,学到了后面在回来修改
释放单例对象
一般情况下我们不会主动释放单例对象,因为单例对象创建后在整个程序运行期间都可能会使用,程序正常结束时会自动将资源归还给操作系统。但存在一些特殊情况需要主动释放。
方法1
在单例类里面提供一个访问释放这个全局对象的静态函数
// 一般单例不用释放。
// 特殊场景:1、中途需要显示释放 2、程序结束时,需要做一些特殊动作(如持久化)
static void Delete()
{
if (_lazy)
{
delete _lazy;
_lazy = nullptr;
}
}
方法2
- 在单例类里面内嵌一个用于垃圾回收的类。
- 在单例类里面定义一个垃圾回收类的静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象。
class Lazy
{
public:
// 实现一个内嵌垃圾回收类
class CGarbo
{
public:
~CGarbo()
{
if (_lazy)
{
delete _lazy;
}
}
};
// 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象
static CGarbo Garbo;
private:
};
Lazy::CGarbo Garbo;