大纲
单例模式
单例模式的概念
单例模式可以确保一个类在程序运行过程中只有一个实例,并提供一个全局访问点。简而言之,就是 “全局唯一的对象”。常用于管理共享资源,比如配置对象、日志系统等。在 C++ 中,为了防止在外部对单例实例化,需要将其默认构造函数设计为私有化,同时删除默认拷贝构造函数和赋值运算符。
单例模式的使用
提示
在 C++ 中使用单例模式时,一般不用管单例对象的内存释放问题,也就是不需要编写自定义的析构函数。
饿汉单例模式
饿汉单例模式是指还没有获取单例对象,单例对象就已经创建完成(初始化)了。值得一提的是,饿汉单例模式是线程安全的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| #include <iostream>
using namespace std;
class ChairMan {
private: ChairMan() { cout << "创建主席" << endl; }
ChairMan(const ChairMan &) = delete;
ChairMan &operator=(const ChairMan &) = delete;
public: static ChairMan *getInstance() { return singleton; }
private: static ChairMan *singleton;
};
ChairMan *ChairMan::singleton = new ChairMan();
void test01() { ChairMan *man1 = ChairMan::getInstance(); ChairMan *man2 = ChairMan::getInstance(); cout << (man1 == man2 ? "true" : "false") << endl; }
int main() { cout << "---- main ----" << endl; test01(); return 0; }
|
程序运行的输出结果如下:
1 2 3
| 创建主席 ---- main ---- true
|
懒汉单例模式
懒汉单例模式是指在第一次被使用时才创建单例对象,而不是程序启动时就创建。特别注意,懒汉单例模式不一定是线程安全的,由具体的代码实现决定。线程安全的懒汉单例模式有以下几种实现方式:
实现方式 | 是否线程安全 | 防止指令重排 | 推荐指数 |
---|
局部静态变量 | ✅(需要 C++ 11 及以上的编译器) | ✅ | ⭐⭐⭐⭐(最推荐,最简洁) |
mutex + atomic + DCL | ✅(兼容 C++ 11 以下的编译器,如 C++ 98 / C++ 03 | ✅ | ⭐⭐⭐(适合对底层控制有要求的场景) |
第一种实现方式
使用 mutex
+ atomic
+ DCL(双重检查锁)实现线程安全的懒汉单例模式,兼容 C++ 11 以下的编译器(如 C++ 98 / C++ 03)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| #include <iostream> #include <mutex> #include <atomic>
using namespace std;
class Singleton {
private: Singleton() noexcept {
}
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton &) = delete;
public: static Singleton *getInstance() { Singleton *instance = _singleton.load(memory_order_acquire);
if (instance == nullptr) { lock_guard<mutex> lock(_mutex);
instance = _singleton.load(memory_order_relaxed);
if (instance == nullptr) { instance = new Singleton();
_singleton.store(instance, memory_order_release); } } return instance; }
static void destroyInstance() { lock_guard<mutex> lock(_mutex);
Singleton *instance = _singleton.exchange(nullptr); if (instance != nullptr) { delete instance; } }
private: static mutex _mutex; static atomic<Singleton *> _singleton;
};
mutex Singleton::_mutex;
atomic<Singleton *> Singleton::_singleton(nullptr);
int main() { Singleton *s1 = Singleton::getInstance(); Singleton *s2 = Singleton::getInstance(); cout << (s1 == s2 ? "true" : "false") << endl; return 0; }
|
程序运行的输出结果如下:
第二种实现方式
若使用的是 C++ 11 及以上编译器,可以使用局部静态变量的线程安全初始化特性来实现懒汉单例模式(这也是线程安全的)。
提示
在 C++ 11 中,局部静态变量的初始化是线程安全的。因为在底层的汇编指令上,已经自动添加上线程互斥指令,所以是线程安全的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #include <iostream>
using namespace std;
class Singleton {
private: Singleton() {
}
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton &) = delete;
public: static Singleton *getInstance() { static Singleton instance;
return &instance; }
};
int main() { Singleton *s1 = Singleton::getInstance(); Singleton *s2 = Singleton::getInstance(); cout << (s1 == s2 ? "true" : "false") << endl; return 0; }
|
程序运行的输出结果如下:
底层原理的剖析
(1) 使用上面第一种方式实现线程安全的懒汉单例模式时,为什么要防止指令重排?
因为在构造 Singleton 对象(_singleton = new Singleton();
)时,底层实际上包含了三步操作,如下所示:
1 2 3
| 1. 分配内存 2. 构造对象 3. 将指针赋值给 _singleton
|
指令重排可能会将步骤 3 提前到步骤 2 之前执行,导致另一个线程看到 _singleton != nullptr
,但其实单例对象还没构造好,使用它就会出现未定义行为。
(2) C++ 的 volatile 并不能防止指令重排
在 Java 中,volatile
是可以防止指令重排的,但在 C++ 中不是这样。在 C++ 中:
volatile
主要用于访问硬件寄存器,或者防止编译器优化。volatile
不能防止 CPU 或编译器对指令进行重排,也不能保证内存可见性(即线程 A 写入的数据,线程 B 就能立刻看到)。
工厂模式
工厂模式的概念
工厂模式提供了一种创建对象的最佳方式。在工厂模式中,开发者在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象,从而实现了创建者和调用者分离。工厂模式分为以下三种类型:
- 简单工厂模式:用来生产同一等级结构的任意产品(不支持拓展增加产品)。
- 工厂方法模式:用来生产同一等级结构的固定产品(支持拓展增加产品)。
- 抽象工厂模式:用来生产不同产品族的全部产品(不支持拓展增加产品,但支持增加产品族)。
工厂模式的使用
简单工厂模式
简单工厂模式(Simple Factory)相当于是一个工厂中有各种产品,创建在一个类中,客户无需知道具体产品的名称,只需要知道产品类所对应的参数即可。但是,工厂的职责过重,而且当类型过多时不利于系统的扩展维护。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| #include <iostream> #include <memory>
using namespace std;
class Car {
public: explicit Car(string name) : _name(name) {
}
virtual void show() = 0;
protected: string _name;
};
class Bmw : public Car {
public: explicit Bmw(string name) : Car(name) {
}
void show() override { cout << "这是一辆宝马 " << _name << endl; }
};
class Audi : public Car {
public: explicit Audi(string name) : Car(name) {
}
void show() override { cout << "这是一辆奥迪 " << _name << endl; } };
enum CarType { BMW, AUDI };
class SimpleFactory {
public: static unique_ptr<Car> createCar(CarType type) { switch (type) { case BMW: return unique_ptr<Car>(new Bmw("X6")); case AUDI: return unique_ptr<Car>(new Audi("A8")); default: cerr << "传入的工厂参数不正确 : " << type << endl; break; } } };
int main() { unique_ptr<Car> car1 = SimpleFactory::createCar(BMW); car1->show();
unique_ptr<Car> car2 = SimpleFactory::createCar(AUDI); car2->show();
return 0; }
|
程序运行的输出结果如下:
总结
简单工厂模式:把对象的创建封装在一个接口函数里面,通过传入不同的标识,返回创建的对象。客户端不用自己负责创建对象,不用了解对象创建的详细过程。
工厂方法模式
工厂方法模式(Factory Method)又称多态性工厂模式。在工厂方法模式中,核心的工厂类不再负责所有的产品的创建,而是将具体创建的工作交给子类去完成。该核心工厂类成为一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| #include <iostream> #include <memory>
using namespace std;
class Car {
public: explicit Car(string name) : _name(name) {
}
virtual void show() = 0;
protected: string _name;
};
class Bmw : public Car {
public: explicit Bmw(string name) : Car(name) {
}
void show() override { cout << "这是一辆宝马 " << _name << endl; }
};
class Audi : public Car {
public: explicit Audi(string name) : Car(name) {
}
void show() override { cout << "这是一辆奥迪 " << _name << endl; } };
class CarFactory {
public: virtual unique_ptr<Car> createCar(string name) = 0;
};
class BmwFactory : public CarFactory {
public: unique_ptr<Car> createCar(string name) override { return unique_ptr<Bmw>(new Bmw(name)); }
};
class AudiFactory : public CarFactory {
public: unique_ptr<Car> createCar(string name) override { return unique_ptr<Audi>(new Audi(name)); }
};
int main() { unique_ptr<CarFactory> bmwFactory(new BmwFactory()); unique_ptr<Car> car1 = bmwFactory->createCar("X6"); car1->show();
unique_ptr<CarFactory> audiFactory(new AudiFactory()); unique_ptr<Car> car2 = audiFactory->createCar("A8"); car2->show();
return 0; }
|
程序运行的输出结果如下:
总结
工厂方法模式:Factory 基类提供了一个纯虚函数(用于创建产品),派生类(创建具体产品的工厂)负责创建对应的产品,可以做到不同的产品,在不同的工厂里面创建。
抽象工厂模式
抽象工厂的主要作用是:提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们的具体类。简单来说,就是对一组有关联关系的产品族提供产品对象的统一创建,如图所示。优点是可以确保创建的产品是相互兼容的,增加新的产品族只需添加一个新的工厂类。缺点是不容易扩展新的产品种类,因为需要修改所有工厂类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
| #include <iostream> #include <memory>
using namespace std;
class Engine {
public: explicit Engine(string name) : _name(name) {
}
virtual void show() = 0;
protected: string _name;
};
class BmwEngine : public Engine {
public: BmwEngine(string name) : Engine(name) {
}
void show() override { cout << "产品名称:" << _name << endl; }
};
class AudiEngine : public Engine {
public: AudiEngine(string name) : Engine(name) {
}
void show() override { cout << "产品名称:" << _name << endl; }
};
class Light {
public: explicit Light(string name) : _name(name) {
}
virtual void show() = 0;
protected: string _name;
};
class BmwLight : public Light {
public: BmwLight(string name) : Light(name) {
}
void show() override { cout << "产品名称:" << _name << endl; }
};
class AudiLight : public Light {
public: AudiLight(string name) : Light(name) {
}
void show() override { cout << "产品名称:" << _name << endl; }
};
class AbstractFactory {
public: virtual unique_ptr<Engine> createEngine(string name) = 0;
virtual unique_ptr<Light> createLight(string name) = 0;
};
class BmwFactory : public AbstractFactory {
public: unique_ptr<Engine> createEngine(string name) override { return unique_ptr<Engine>(new BmwEngine(name)); }
unique_ptr<Light> createLight(string name) override { return unique_ptr<Light>(new BmwLight(name)); }
};
class AudiFactory : public AbstractFactory {
public: unique_ptr<Engine> createEngine(string name) override { return unique_ptr<Engine>(new AudiEngine(name)); }
unique_ptr<Light> createLight(string name) override { return unique_ptr<Light>(new AudiLight(name)); } };
int main() { unique_ptr<AbstractFactory> bmwFactoy(new BmwFactory()); unique_ptr<Engine> engine1 = bmwFactoy->createEngine("宝马 X6 引擎"); unique_ptr<Light> light1 = bmwFactoy->createLight("宝马 X6 大灯"); engine1->show(); light1->show();
unique_ptr<AbstractFactory> audiFactoy(new AudiFactory()); unique_ptr<Engine> engine2 = audiFactoy->createEngine("奥迪 A8 引擎"); unique_ptr<Light> light2 = audiFactoy->createLight("奥迪 A8 大灯"); engine2->show(); light2->show();
return 0; }
|
程序运行的输出结果如下:
1 2 3 4
| 产品名称:宝马 X6 引擎 产品名称:宝马 X6 大灯 产品名称:奥迪 A8 引擎 产品名称:奥迪 A8 大灯
|
总结
- 实际上,很多产品是有关联关系的,往往属于同一个产品族,不应该放在不同的工厂里面去创建,这样一是不符合实际的产品对象创建逻辑,二是工厂类太多不好维护。
- 抽象工厂模式:将有关联关系的、属于同一个产品族的所有产品的创建接口函数放在一个抽象工厂里面,派生类(创建具体产品的工厂)负责创建该产品族里面的所有产品。
代理模式
代理模式的概念
代理模式(Proxy Pattern)是一种常见的结构型设计模式,它的核心思想是:为其他对象提供一种代理以控制对这个对象的访问。代理对象在客户端和目标对象之间起到了一个中介的作用,可以在不改变目标对象的前提下,增强或控制其行为。简而言之,代理模式就是:” 有事找我,我来转告(或者处理)”。
代理模式的结构组成
- 抽象主题(Subject):定义代理类和真实主题类的共同接口,使得代理对象可以替代真实对象。
- 真实主题(RealSubject):真正执行实际操作的对象。
- 代理类(Proxy):持有真实对象的引用,通过代理类控制对真实对象的访问。
代理模式的使用场景
- 远程代理:为远程对象提供本地代理(比如 Java RMI)。
- 虚拟代理:根据需要创建资源开销大的对象(比如延迟加载大图片)。
- 安全代理:控制真实对象的访问权限(比如权限验证)。
- 智能代理:在访问对象时进行一些附加操作(比如日志记录、计数等)。
代理模式的优缺点
- 优点:
- 可以在不修改原有类的基础上扩展功能。
- 控制对真实对象的访问,提高安全性。
- 隐藏真实对象的实现细节。
- 缺点:
- 增加系统设计的复杂度。
- 可能导致请求处理速度变慢(因为增加了一层间接访问)。
代理模式的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
| #include <iostream> #include <memory>
using namespace std;
class VideoApp {
public: virtual void freeMovie() = 0;
virtual void vipMovie() = 0;
virtual void ticketMovie() = 0;
virtual ~VideoApp() {};
};
class QuickVideoApp : public VideoApp {
public: void freeMovie() override { cout << "观看免费电影" << endl; }
void vipMovie() override { cout << "观看 VIP 电影" << endl; }
void ticketMovie() override { cout << "用券观看电影" << endl; }
};
class FreeVideoAppProxy : public VideoApp {
public: FreeVideoAppProxy() { _app = new QuickVideoApp(); }
~FreeVideoAppProxy() { delete _app; }
void freeMovie() override { _app->freeMovie(); }
void vipMovie() override { cout << "您目前只是普通用户,需要升级成为 VIP 用户,才能观看电影" << endl; }
void ticketMovie() override { cout << "您的账户没有电影券,需要购买电影券,才能观看电影" << endl; }
private: VideoApp *_app;
};
class VipVideoAppProxy : public VideoApp {
public: VipVideoAppProxy() { _app = new QuickVideoApp(); }
~VipVideoAppProxy() { delete _app; }
void freeMovie() override { _app->freeMovie(); }
void vipMovie() override { _app->vipMovie(); }
void ticketMovie() override { cout << "您的账户没有电影券,需要购买电影券,才能观看电影" << endl; }
private: VideoApp *_app;
};
void watchMovie(unique_ptr<VideoApp> &app) { app->freeMovie(); app->vipMovie(); app->ticketMovie(); }
int main() { unique_ptr<VideoApp> freeApp(new FreeVideoAppProxy()); watchMovie(freeApp);
cout << "--------------------------------------------------------" << endl;
unique_ptr<VideoApp> vipApp(new VipVideoAppProxy()); watchMovie(vipApp);
return 0; }
|
程序运行的输出结果如下:
1 2 3 4 5 6 7
| 观看免费电影 您目前只是普通用户,需要升级成为 VIP 用户,才能观看电影 您的账户没有电影券,需要购买电影券,才能观看电影 -------------------------------------------------------- 观看免费电影 观看 VIP 电影 您的账户没有电影券,需要购买电影券,才能观看电影
|