目录
一、设计一个类,不能被拷贝
要设计一个不能被拷贝的类有两种方式:
1、将拷贝构造和赋值重载设为私有
2、用 delete 关键字,将拷贝构造和赋值重装设为删除状态
// 1、用 delete
class NoCopy1
{
public:
NoCopy1()
{}
NoCopy1(const NoCopy1& nc) = delete;
NoCopy1& operator=(const NoCopy1& nc) = delete;
int a = 2;
};
// 2、将拷贝构造和赋值重载设为私有
class NoCopy2
{
public:
NoCopy2()
{}
private:
NoCopy2(const NoCopy2& nc);
NoCopy2& operator=(const NoCopy2& nc);
int a = 2;
};
二、设计一个类,只能在堆上创建
设计一个只能在堆上创建的类同样有两种方法:
1、将析构函数设为私有
将析构函数设为私有后,因为析构私有,在栈上无法对象。而用 new 在堆上创建对象是调用 delete 释放空间,而 delete 也是在类内,可以调用析构。由此实现了只能在堆上创建的类。
2、将构造私有,对外提供一个接口,外部只能通过调用这个接口才能创建对象,而这个接口我们可以自己实现在堆上创建。由此,可以实现只能在堆上创建的类。(通用方法)
注意:使用第二种方法时,我们需要将拷贝构造和赋值重载一并私有,因为外部可以用 HeapOnly2* ho2(*ho1); 在栈上创建。(ho1 是用 create 拿到的指针)
// 方法1,将析构函数设为私有
class HeapOnly1
{
public:
private:
// 方法:析构函数设为私有
~HeapOnly1()
{}
};
// 方法2,将构造函数设为私有,给出 create 接口,用 new 创建并返回对象 -- 通用方法,封构造
// 注意:要把拷贝构造和赋值一并封住,防止拷贝
class HeapOnly2
{
public:
static HeapOnly2* create()
{
HeapOnly2* ho = new HeapOnly2;
return ho;
}
private:
HeapOnly2()
{}
HeapOnly2(const HeapOnly2& ho) = delete;
};
三、设计一个类,只能在栈上创建
要设计一个只能在栈上创建的类,我们可以用上面的通用方法,将构造函数私有化,对外提供一个接口,外部只能通过这个接口拿到对象。
这里不需要将拷贝构造和赋值重载私有,因为它们也是在栈上创建对象
class StackOnly
{
public:
static StackOnly create()
{
return StackOnly();
}
private:
StackOnly()
{}
};
四、设计一个类,不能被继承
设计一个不能被继承的类通用有两种方式:
1、C++98:将构造函数私有,它就无法被继承
2、C++11:用 final 关键字
// 方法1,C++98方式,将构造私有
class NoInherit
{
private:
NoInherit()
{}
};
// 方法2,final 关键字,表示类不能被继承
class NoInherit final
{};
五、单例模式
单例模式:一个类只能创建一个实例。该模式可以保证全局系统中,该类只有一个实例,并提供一个全局访问点,该实例被所有模块共享。
实现方法:
1、饿汉模式
(假设该类的名字为 HungrySingleton)
①、将构造、拷贝构造、赋值重载封住
②、定义一个静态成员 static HungrySingleton* _instance;
③、对外提供一个接口,外部只能调用这个接口获得实例,返回的实例就是 _instance
叫饿汉模式的原因,就是静态对象是在 main 函数之前创建。
// 饿汉模式:一开始就创建对象(静态成员在 main 函数之前就创建)
class HungrySingleton
{
public:
static HungrySingleton* GetInstance()
{
return _instance;
}
private:
HungrySingleton()
{}
HungrySingleton(const HungrySingleton& hst) = delete;
HungrySingleton& operator=(const HungrySingleton& hst) = delete;
private:
static HungrySingleton* _instance;
int _n = 1;
vector<int> _v;
};
HungrySingleton* HungrySingleton::_instance = new HungrySingleton;
2、懒汉模式
(假设该类的名字为 LazySingleton)
①、将构造、拷贝构造、赋值重载封住
②、定义一个静态成员 static LazySingleton* _instance;
③、与饿汉模式不同,懒汉模式是需要时,才创建。也就是说:当多个线程想实例化对象时,只有第一个线程创建,其他线程都是拿第一个线程创建好的。
class LazySingleton
{
public:
static LazySingleton* GetInstance()
{
// 双检查加锁,创建实例后来的线程不用竞争锁,提高效率
if (nullptr == _lazy_instance)
{
// 可能有多个线程同时进来,加锁保护
unique_lock<mutex> mtx;
if (nullptr == _lazy_instance)
{
// 第一个进来的线程创建实例
cout << "创建对象\n";
_lazy_instance = new LazySingleton;
return _lazy_instance;
}
}
// 后面的线程就获得
cout << "获得对象\n";
return _lazy_instance;
}
private:
LazySingleton()
{}
LazySingleton(const LazySingleton& hst) = delete;
LazySingleton& operator=(const LazySingleton& hst) = delete;
private:
static LazySingleton* _lazy_instance;
static mutex _mtx;// 加锁保护
int _n = 1;
vector<int> _v;
};
LazySingleton* LazySingleton::_lazy_instance = nullptr;
mutex LazySingleton::_mtx;
④、释放单例:一般单例不需要释放,因为全局都要使用。但有些特殊情况,如果要实现释放单例,我们也可以对外提供接口,防止多个线程同时释放,同样需要加锁保护。
同时,在这个情况下,当程序运行结束时,单例也需要释放,因此,我们不仅要提供一个释放单例的接口,还要想办法让程序结束时能够自动调用它。
解决方法:在 LazySingleton 类中写一个内部类 GC,GC 的析构函数就调用释放单例接口,并定义一个静态成员 static GC* _gc; 当程序运行结束,就会自动调用释放接口了。
class LazySingleton
{
public:
static LazySingleton* GetInstance()
{
// 双检查加锁
if (nullptr == _lazy_instance)
{
unique_lock<mutex> mtx;
if (nullptr == _lazy_instance)
{
cout << "创建对象\n";
_lazy_instance = new LazySingleton;
return _lazy_instance;
}
}
cout << "获得对象\n";
return _lazy_instance;
}
static void DelInstance()
{
// 一般全局都有使用单例,一般不用释放单例
// 只有特殊场景才需要
unique_lock<mutex> lock(_mtx);
if (_lazy_instance)
{
delete _lazy_instance;
_lazy_instance = nullptr;
}
}
~LazySingleton()
{
// 持久化,将数据写到文件
}
// 结束后自动调用 DelInstance
class GC
{
public:
~GC()
{
cout << "释放单例\n";
DelInstance();
}
};
private:
LazySingleton()
{}
LazySingleton(const LazySingleton& hst) = delete;
LazySingleton& operator=(const LazySingleton& hst) = delete;
private:
static LazySingleton* _lazy_instance;
static mutex _mtx;
static GC _gc;
int _n = 1;
vector<int> _v;
};
LazySingleton* LazySingleton::_lazy_instance = nullptr;
mutex LazySingleton::_mtx;
LazySingleton::GC LazySingleton::_gc;
饿汉模式和懒汉模式对比
饿汉缺点
1、如果单例对象很大,main 函数之前申请,a、占用资源;b、影响程序启动速度
2、如果两个单例有依赖关系,要求单例1先创建,单例2再创建,饿汉无法控制顺序
饿汉优点:简单
懒汉解决了饿汉的问题(推荐懒汉)