C++ 设计模式(单例+工厂)
1.单例模式
推荐一个讲解单例视频,整体概述比较全面:【视频 C++单例模式:从设计到实现】
工作具体项目中的应用,可以看看这个视频,讲了单例的各种用法:【视频 C++单例模式】
下面是我自己从上面视频中的整理的一些讲解的理解和代码实现,主要是懒汉式单例模式。
(1).概念
通过单例模式的方法创建的类在当前进程中有且仅有一个实例。单例模式,属于创建类型的一种常用的软件设计模式。
具体细分:
共同点:
01.要声明一个静态的类引用变量
02.类的构造函数要私有
03.提供一个公有的创建对象的方法,能够全局访问
区别:
懒汉式单例是在方法调用时创建对象,而饿汉式是在类加载是创建对象;
多线程情况下,懒汉式单例存在线程安全问题,饿汉式不存在线程安全问题。
(2).应用场景
对程序运行期间对全局唯一资源的统一访问。
比如,配置管理、日志记录、线程池、连接池、内存池、对象池、消息队列等。
自己最近学习的tcp服务端,就用到了单例模式(懒汉式),这种写法是使用局部静态变量方式(与进程生命周期同步);
可以避免进行释放内存操作,这种单例模式的写法比较推荐;
这是因为静态局部变量在C++11标准之后的编译器实现中会进行线程安全的初始化,保证局部变量初始化严格发生一次。
在初始化过程中,编译器会确保只有一个线程能够执行该静态局部变量的初始化代码,从而避免了多线程竞争的问题。
具体的说明,可以看这篇文档,【文档 静态局部变量】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
class Singleton { private: Singleton(){} Singleton(const Singleton &) = delete; Singleton &operator=(const Singleton &) = delete; public: static Singleton &GetInst() { static Singleton single; return single; } };
|
具体项目中应用的例子,
(3).实现单例模式(懒汉式较常用)
在大的实际项目中可能有多个类用到了单例,不可能把他们单独放在一起集中来初始化,所以多用懒汉式单例,具体使用时来创建单例。
(3-1).单例模式实现(懒汉+饿汉)
懒汉式单例,比较常用
代码实现:a.h
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
| #pragma once #include <iostream> #include <string>
class A{
private: A() : m_name("A") {} A(const A &other) {} ~A() {} A& operator = (const A &other);
private: static A* m_instance; std::string m_name;
public: static A* instance() { if(m_instance == nullptr) { m_instance = new A(); } return m_instance; } void show() { std::cout << "m_name: " << m_name << std::endl; } };
A* A::m_instance = nullptr;
|
主程序,main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include <iostream> #include "a.h" using namespace std;
int main() { A* a1 = A::instance(); a1->show(); cout << "a1: " << a1 << endl; auto a2 = A::instance(); a2->show(); cout << "a2: " << a2 << endl; return 0; }
|
代码运行结果,
1 2 3 4
| m_name: A a1: 0x55b10376d270 m_name: A a2: 0x55b10376d270
|
饿汉式单例(不常用),是线程安全的
代码实现:Singleton.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| # pragma once class Singleton {
private: Singleton() {} static Singleton* instance;
public: static Singleton* getSingleton() { return instance; } };
Singleton* Singleton::instance = new Singleton();
|
具体使用,main.cpp
1 2 3 4 5 6 7 8 9 10 11 12
| #include <iostream> #include "Singleton.h"
int main() {
Singleton* a1 = Singleton::getSingleton(); Singleton* a2 = Singleton::getSingleton(); std::cout << "a1's addr: " << a1 << std::endl; std::cout << "a2's addr: " << a2 << std::endl; return 0; }
|
运行结果如下,
1 2
| a1's addr: 0x6c1780 a2's addr: 0x6c1780
|
(3-2).用类模板优化写法(懒汉式)
定义一个类模板,singleton.h
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
| #pragma once namespace ariesfun { namespace utility { template <typename T> class Singleton {
public: static T* instance() { if(m_instance == nullptr) { m_instance = new T(); } return m_instance; } private: Singleton() {} Singleton(const Singleton<T> &another); ~Singleton() {} Singleton<T>& operator = (const Singleton<T> &another); private: static T* m_instance; };
template <typename T> T* Singleton<T>::m_instance = nullptr; } }
|
改写a.h
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
| #pragma once #include <iostream> #include <string>
#include "singleton.h" using namespace ariesfun::utility;
class A{
private: A() : m_name("A") {} A(const A &other) {} ~A() {} A& operator = (const A &other);
private: std::string m_name;
public: void show() { std::cout << "m_name: " << m_name << std::endl; } friend class Singleton<A>; };
|
新的main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include <iostream> #include "a.h"
using namespace std;
#include "singleton.h" using namespace ariesfun::utility;
int main() { auto sa1 = Singleton<A>::instance(); sa1->show(); cout << "sa1: " << sa1 << endl;
auto sa2 = Singleton<A>::instance(); sa2->show(); cout << "sa2: " << sa2 << endl; return 0; }
|
代码运行结果,
1 2 3 4
| m_name: A sa1: 0x5568f0b07270 m_name: A sa2: 0x5568f0b07270
|
(3-3).其他拓展
你可能见到过有些官方源码还有其他人,写法是宏定义或者在使用默认构造、拷贝、赋值后,加上了一些关键字(defalut,delete),这是一种优化的写法。
比如,
1 2 3 4 5 6 7
| #define SINGLETON(classname) \ friend class Singleton<classname>; \ private: \ classname() = default; \ classname(const classname &) = delete; \ classname & operator = (const classname &) = delete; \ ~classname() = default
|
(1).private:
: 这是一个访问权限标识符,表示以下成员都将是私有的,只能在类的内部访问。
(2).classname() = default;
: 表示使用编译器生成默认的构造函数实现。在单例模式中,通常会将构造函数设为私有,以防止从外部直接创建类的实例。
(3).classname(const classname &) = delete;
: 使用 = delete
表示禁用拷贝构造函数。这样做是为了防止通过拷贝构造函数创建多个实例,从而维护单例的唯一性。
(4).classname & operator=(const classname &) = delete;
: 使用 = delete
表示禁用赋值操作符。这也是为了避免通过赋值操作创建多个实例。
(5).~classname() = default;
: 使用 = default
表示使用编译器生成默认的析构函数实现。在单例模式中,通常不需要特殊的析构函数逻辑。
这段代码的目的是创建一个单例模式的类,其中通过私有化构造函数、拷贝构造函数和赋值操作符,以及声明友元类,来确保只有 Singleton
类能够创建和管理classname
类的唯一实例。
使用 = default
和 = delete
是一种简洁的方式来指定默认的函数实现或禁用特定的函数。
(4).考虑多线程下的安全问题(懒汉式)
最佳解决是直接规避掉这个问题,而不是找方法去解决这个问题(加锁等)。 乱码哥牛啊,哈哈
其他加锁的方法,实际有需求或遇到线程安全问题,可以参考一下这篇博客介绍的解决方案:【文档 再谈单例模式】
乱码哥建议,可在main函数中这样使用,
1 2 3 4 5 6 7 8 9
|
Singleton<A>::instance(); Singleton<B>::instance();
Singleton<A>::instance()->show(); Singleton<B>::instance()->show();
|
2.工厂模式
(1).概念
工厂模式:原来是要自己创建对象,现在是不需要自己创建对象,而是创建这个对象的工厂。
工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。
百度百科-工厂模式就相当于创建实例对象的new,我们经常要根据类class
生成实例对象,
如A a=new A();
工厂模式也是用来创建实例对象的,所以以后new时就要多个心眼,是否可以考虑使用工厂模式,
虽然这样做,可能多做一些工作,但会给你系统带来更大的可扩展性和尽量少的修改量。
(2).应用场景
工厂模式的主要目的,是封装对象的创建过程,提供更高层次的抽象,降低代码之间的耦合度,使代码更加可扩展和可维护。
让我们来学习一下ChatGpt的回答,讲得倒是很全面。
(3).实现工厂模式(工厂方法模式)
工厂方法模式是一种创建型设计模式,用于创建对象的接口在父类中定义,但是让子类决定实例化的类是哪一个。
在工厂方法模式中,工厂类负责创建产品对象,具体的产品创建由子类的工厂类决定。
下面用创建一个车的工厂来举例,
先定义两个父类的头文件,车类和车工厂类,并尝试用CMake管理,采用多文件编写。
项目目录如下,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| . ├── CMakeLists.txt ├── docs ├── include │ ├── Baoma.h │ ├── BaomaFactory.h │ ├── Benchi.h │ ├── BenchiFactory.h │ ├── Car.h │ ├── CarFactory.h │ └── singleton.h # 可以忽略,这是测试单例模式时使用的类 └── src ├── Baoma.cpp ├── BaomaFactory.cpp ├── Benchi.cpp ├── BenchiFactory.cpp └── main.cpp
|
Car.cpp
1 2 3 4 5 6 7 8 9 10 11 12
| #ifndef FACTORY_PATTERN_CAR_H #define FACTORY_PATTERN_CAR_H
#include <string> class Car {
public: virtual std::string get_name() = 0; Car() = default; };
#endif
|
CarFactory.h
1 2 3 4 5 6 7 8 9 10 11 12
| #ifndef FACTORY_PATTERN_CARFACTORY_H #define FACTORY_PATTERN_CARFACTORY_H
#include "Car.h" class CarFactory {
public: virtual Car* getCar() = 0;
};
#endif
|
当我们需要创建一个车对象(奔驰车)时,我们需要创建这个车和对于这个车工厂(继承各自父类),当我们要使
用这个车时,直接从它工厂里拿就行。
下面以创建奔驰车对象为例,
奔驰车类,Benchi.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #ifndef FACTORY_PATTERN_BENCHI_H #define FACTORY_PATTERN_BENCHI_H
#include "Car.h" class Benchi :public Car {
public: std::string get_name() override; };
#endif
"Benchi.cpp" #include "Benchi.h"
std::string Benchi::get_name() { return "benchi"; }
|
奔驰车工厂,BenchiFactory.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #ifndef FACTORY_PATTERN_BENCHIFACTORY_H #define FACTORY_PATTERN_BENCHIFACTORY_H
#include "CarFactory.h"
class BenchiFactory :public CarFactory {
public: Car* getCar() override; };
#endif
"BenchiFactory.cpp" #include "BenchiFactory.h" #include "Benchi.h"
Car* BenchiFactory::getCar() { return new Benchi(); }
|
在这个例子中,基类CarFactory
定义了一个抽象的getCar
函数,由子类来实现具体的创建过程。
BenchiFactory
是一个具体的子类,它继承了CarFactory
并实现了getCar
函数,返回一个Benchi
对象。
这种模式的好处是,当需要添加新的汽车品牌时,只需创建一个新的子类并实现相应的getCar
函数即可,不会对
已有的代码产生影响。
编写测试类,main.cpp
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
| #include <iostream>
#include "Car.h"
#include "Baoma.h" #include "BaomaFactory.h"
int main() {
Car* c1 = (new BenchiFactory)->getCar(); std::cout << "Car1 name: " << c1->get_name() << std::endl;
return 0; }
|
参考资料
单例模式:
【文档 单例模式】
【文档 再谈单例模式】
【视频 C++单例模式:从设计到实现】
【视频 C++单例模式】
【视频 C++单例模式总结】
【视频 Java单例设计模式】
工厂模式:
【视频 C/C++项目实战-前置知识】
【仓库代码 SYaoJun/SystemProgramming】