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】