1、单例模式
一个类只有一个实例,提供一个全局访问点来访问这个实例。
分为懒汉模式和饿汉模式:
- 懒汉模式就是 只有用到这个实例才会初始化对象并返回 (调用了对外的接口才实例化对象)
- 饿汉模式就是 不管用不用得到,都先构造出来,先初始化
双重锁的懒汉模式:
#include <mutex>class SingeLazy {
private:SingeLazy() {}SingeLazy(const SingleLazy& ) = delete;SingeLazy& operator= (const SingleLazy& ) = delete;static SingleLazy* instance;static mutex i_mutex;public:static SingleLazy* getInstance() {if (instance == nullptr) {mutex.lock();if (instance == nullptr) {instance = new SingleLazy();}mutex.unlock();}return instance;
}
};
SingleLazy* SingleLazy::instance = nullptr;
mutex SingleLazy::i_mutex; // 类外初始化
双重锁机制(Double-Checked Locking)用于确保单例对象只被创建一次,并且提高线程安全性和性能。
为什么要进行第二次检查 if (instance == nullptr)
?
当第一个线程获得锁并创建单例对象时,其他等待的线程可能已经通过了第一次检查 if (instance == nullptr)
,并准备进入加锁阶段。如果没有第二次检查,当第一个线程释放锁之后,第二个线程会获得锁并尝试再次创建对象。
场景描述:
- 线程A进入 getInstance(),通过了第一次检查,发现 instance == nullptr,然后加锁并创建实例。
- 线程B几乎同时进入 getInstance(),也通过了第一次检查,因为当时 instance 还没被创建,但它被阻塞在锁等待状态。
- 当线程A创建了实例并释放锁,线程B获得锁,并进入锁定区域。如果没有第二次检查,线程B会认为 instance 仍然是
nullptr,并再次尝试创建实例。
因此,第二次检查的作用是防止在第一个线程创建实例后,其他线程重复创建实例。
2、非拷贝模式与父子类继承
/*
派生类在调用构造函数和析构函数时会首先调用基类的构造函数和析构函数noncopyable 被继承后 派生类对象可以正常调用 构造函数 和 析构函数
但是派生类对象无法使用拷贝构造函数 和 赋值操作
*/
class noncopyable
{
public:noncopyable(const noncopyable&) = delete; // 拷贝noncopyable& operator=(const noncopyable&) = delete; // 赋值protected:noncopyable() = default;~noncopyable() = default;
};class Channel : noncopyable
{...
};
这种模式常见的应用场景包括:
- 资源管理类(如文件、线程、内存管理类):禁止拷贝,防止多个对象持有相同的资源句柄。
- 单例模式(Singleton):单例对象通常不允许被拷贝和赋值,以确保应用中只有一个实例存在。
- 智能指针类(如 std::unique_ptr):独占所有权的智能指针类禁止拷贝,以确保资源只由一个对象管理。
除了这个之外,还需要注意一个知识点:
如果基类是 class
(如 noncopyable),则默认继承权限为 私有继承
。
如果基类是 struct
,则默认继承权限为 公有继承
。
所以 noncopyable 类中的 public 和 protect 成员都在 Channel 类中变为 private 成员,只允许类内调用,不允许 Channel 所创建对象调用。
有关继承的权限问题:
1、public继承:父类public在子类继承为public,protected在子类中继承为protected,private不被继承(这也是protected和private的区别);
2、public继承:父类public和protected在子类中都继承为protected;
3、private继承:父类public和protected在子类中都继承为private。