当前位置: 首页 > news >正文

施磊老师高级c++(五)

文章目录

    • 一、设计模式
    • 二、单例模式(创建型模式)- 重点(共三种代码)
      • 1.1 饿汉式单例模式 -- 不受欢迎
      • 1.2 懒汉式单例模式 -- 受欢迎
      • 1.3 线程安全的懒汉式单例模式--锁+volatile
    • 三、工厂模式(创建型模式)
      • 3.1 简单工厂模式
      • 3.2 工厂方法模式
      • 3.3 抽象工厂模式
      • 工厂模式总结--特点

单例模式与工厂模式

一、设计模式

设计模式是一种解决特定问题的预定义优秀代码框架,相较于自己摸索解决方案,它具有以下优点:

  1. 易维护:代码更易于维护,具有良好的可读性、复用性、可移植性和健壮性。
  2. 适应需求变化:设计模式的应用遵循 “开闭原则”(对修改关闭,对扩展开放),使得在需求变更或增加新功能时,能够灵活调整。
  3. 模块化设计:合理选择设计模式可以实现高内聚、低耦合的模块化设计,遵循软件设计的基本原则。

设计模式的分类:

  • 创建型模式:关注于对象的创建过程,尝试将对象的创建与使用分离,以增加系统的灵活性和可维护性
  • 结构型模式:关注于如何将类或对象组合成更大的结构,以解决更大的问题
  • 行为型模式:关注于对象之间的交互和职责分配,以实现更好的系统行为和更灵活的协作

23种设计模式:

  • 创建型模式(5种):单例模式(Singleton)、工厂方法模式(Factory Method)、抽象工厂模式(Abstract Factory)、建造者模式(Builder)、原型模式(Prototype)
  • 结构型模式(7种):适配器模式(Adapter)、桥接模式(Bridge)、装饰器模式(Decorator)、组合模式(Composite)、外观模式(Facade)、享元模式(Flyweight)、代理模式(Proxy)
  • 行为型模式(11种):模板方法模式(Template Method)、命令模式(Command)、迭代器模式(Iterator)、观察者模式(Observer)、中介者模式(Mediator)、备忘录模式(Memento)、访问者模式(Visitor)、状态模式(State)、策略模式(Strategy)、职责链模式(Chain of Responsibility)、解释器模式(Interpreter)

二、单例模式(创建型模式)- 重点(共三种代码)

在此之前, 先了解一下 static 关键字

static 用法作用补充说明
静态局部变量变量不会在函数调用结束后销毁,值会保留
变量在函数调用结束后依然存在(局部静态变量)
- 仅在定义它的函数内可见
- 初次声明时初始化一次,后续调用不会重新初始化
-会从栈区移动到数据区
静态全局变量仅在当前文件可见,防止命名冲突
限制变量作用范围,使其仅在当前文件可见(内部链接)
- 作用域限制在定义它的文件中,不会污染其他文件的命名空间
静态成员变量属于类,而不是对象,所有对象共享
变量属于类,而不是对象,所有对象共享一份数据
- 需在类内声明, 类外部定义和初始化 (ClassName::variableName = value;)
- 通过 类名::变量名 访问
静态成员函数属于类,不依赖对象,不能访问非静态成员
函数属于类,而不是对象,不能访问非静态成员
- 无 this 指针,不能访问实例成员变量/方法
- 通过 类名::函数名 访问
-其内部只能访问 静态 成员, 不能访问不同的成员
静态全局函数仅在当前文件可见,防止命名冲突
限制函数作用范围,使其仅在当前文件可见
- 仅在定义它的文件中可见,外部文件无法调用

单例模式:一个类不管创建多少次对象,永远只能得到该类类型的唯一一个实例对象,那么设计一个单例就必须要满足下面几个条件

  1. 构造函数私有化:通过将构造函数私有化,防止外部直接创建实例,从而控制实例的创建
  2. 定义该类的唯一实例:在类内部定义一个该类的静态实例并确保只有一个实例被创建
  3. 通过静态方法返回唯一实例:提供一个静态方法用于访问或获取这个唯一实例,确保每次调用返回相同的实例
  4. 删除拷贝构造和赋值运算符重载:防止通过拷贝构造函数或赋值操作符创建新的实例

单例模式分类:

  1. 饿汉式单例模式
    • 特点:实例在类加载时就被创建,无论是否需要使用实例
    • 优点:天然线程安全,因为实例在类加载过程中创建,不存在多线程访问问题
    • 缺点:即使没有使用实例,资源也会被占用,可能导致资源浪费
  2. 懒汉式单例模式
    • 特点:实例在第一次访问时才被创建,采用延迟加载机制
    • 优点:延迟实例化,只有在需要时才创建实例,节省资源
    • 缺点线程不安全,需要处理多线程环境中的同步问题,以确保实例的唯一性,可能影响性能

单例模式的应用场景举例:

  • 日志模块:整个应用通常只需要一个日志模块实例,以统一管理日志的输出
  • 数据库模块:在客户端和服务器端之间进行通信时,使用单例模式可以确保只有一个数据库连接实例,从而减少资源消耗和连接管理的复杂性
  • 配置管理:应用程序中配置数据通常是全局的,并且需要统一访问,可以使用单例模式来管理
  • 线程池:使用单例模式可以确保只有一个线程池实例,便于统一管理和调度线程
  • 缓存:单例模式可以用于实现应用的全局缓存,以提高访问速度和性能

1.1 饿汉式单例模式 – 不受欢迎

  1. 什么是饿汉式单例模式?
    饿汉式单例(Hungry Singleton)是一种在类加载时就立即实例化对象的单例模式

    还没有获取实例对象, 实例对象 就已经产生了

  2. 饿汉式 一定是 线程安全的?

    因为 静态的成员变量 在数据段(.data), 数据段 程序已启动 就初始化好了

    饿汉式单例本身在静态变量的初始化中一般是线程安全的,但需要注意静态初始化顺序的问题,尤其在多线程环境和动态库情况下。为了确保绝对的线程安全,可以使用一些额外的同步机制(例如 std::call_once 或互斥锁)来避免潜在的竞争条件。

  3. 缺点:

    资源浪费(主要):即使单例未被使用,类加载时就会创建实例,导致不必要的资源消耗。

    不支持延迟初始化:实例在程序启动时就创建,无法根据需要延迟创建。

    初始化顺序问题:在静态变量初始化时可能会引发顺序问题,导致错误或未定义行为。

    无法处理复杂并发需求:对复杂资源或多线程环境下的初始化需求处理不足。

    生命周期管理困难:无法灵活控制单例的销毁和生命周期,难以处理动态加载和卸载。

代码示例:

#include <iostream>
#include <thread>
#include <list>
#include <mutex>
#include <queue>
#include <condition_variable>
using namespace std;

class Singleton
{
public:
	// 3. 提供一个静态方法返回唯一实例的引用
	static Singleton* getInstance() { return &instance; }

private:
	static Singleton instance;	// 2. 定义该类的唯一实例对象(使用静态变量,在数据段上)
	Singleton() { cout << "Singleton()" << endl; }	// 1.构造函数私有化
	~Singleton() { cout << "~Singleton()" << endl; }

	// 4. 删除拷贝构造和赋值函数 - 不能让他使用默认的
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
};

Singleton Singleton::instance;	// 类外初始化静态成员变量

int main()
{
    // 还没有调用, 实例对象就创建了
	Singleton* p1 = Singleton::getInstance(); // 静态成员函数, 需要使用 类名::函数名 使用
	Singleton* p2 = Singleton::getInstance(); 
	Singleton* p3 = Singleton::getInstance();

	//Singleton t = *p1;  // 单例模式不允许, 需要删除类内 拷贝和赋值函数

	return 0;
}



测试结果:

Singleton()
008EE1E0
008EE1E0
008EE1E0
~Singleton()

可以看到,该程序通过饿汉式单例模式实现了单例的创建和管理,确保了整个程序中Singleton类只有一个实例。程序输出验证了单例模式的正确性,所有获取的引用都指向同一个实例

1.2 懒汉式单例模式 – 受欢迎

  1. 什么是懒汉式单例?

    懒汉式单例(Lazy Singleton)是单例设计模式的另一种实现方式,与饿汉式单例不同,它是在第一次需要实例化时才创建对象,而不是在程序启动时就立即创建。

代码示例:

#include <iostream>
#include <thread>
#include <list>
#include <mutex>
#include <queue>
#include <condition_variable>
using namespace std;

class Singleton
{
public:
	
	static Singleton* getInstance() 
	{
		if (instance == nullptr)  //#3 延迟实例化
		{
			instance = new Singleton();
		}
		return instance; 
	}
	static void destroyInstance()
	{
		delete instance;
	}

private:
	static Singleton* instance;	  // #1 修改为指针, 更好的是使用智能指针, 为了自动析构, 这种一般指针, 还得写 销毁函数, 手动释放
	Singleton() { cout << "Singleton()" << endl; }	
	~Singleton() { cout << "~Singleton()" << endl; }


	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
};

Singleton* Singleton::instance=nullptr;	// #2 初始化为空

int main()
{
	Singleton* p1 = Singleton::getInstance(); 
	Singleton* p2 = Singleton::getInstance(); 
	Singleton* p3 = Singleton::getInstance();


	cout << p1 << endl;
	cout << p2 << endl;
	cout << p3 << endl;

	Singleton::destroyInstance();

	return 0;
}

测试结果:

Singleton()
00F20548
00F20548
00F20548
~Singleton()

1.3 线程安全的懒汉式单例模式–锁+volatile

  1. 可重入函数(Reentrant Function)是指在多线程或中断处理的环境下,函数能够安全地被多个线程或中断同时调用,即使在执行过程中该函数被再次调用,也能保证不会产生数据不一致或程序崩溃

  2. 上面实现的懒汉式单例模式是有问题

    其中getInstance函数不是可重入函数

    根源在于:

    会有线程安全的问题getInstance三个步骤:开辟内存、构造对象、给instance赋值(编译器翻译成汇编指令的顺序或者也可以是开辟内存、给instance赋值、构造对象), 不是原子操作

无论两种情况中的哪种,只要是线程一在给instance赋值之前,如果有线程二进入此函数,就会造成线程不安全。

加锁的懒汉单例

#include <iostream>
#include <thread>
#include <list>
#include <mutex>
#include <queue>
#include <condition_variable>
using namespace std;

std::mutex mtx;
class Singleton
{
public:
	
	static Singleton* getInstance() 
	{
		//lock_guard<std::mutex> guard(mtx);  // 这个位置 锁力度太大
		if (instance == nullptr)  
		{
			lock_guard<std::mutex> guard(mtx); // 只在第一次实例化时 加锁, 这有这一块 是步骤多的, 不是原子操作
			// 加锁后, 还要判断
			if (instance == nullptr)
			{
				instance = new Singleton();
			}
			
		}
		return instance; 
	}
	static void destroyInstance()
	{
		delete instance;
	}

private:
	static Singleton *volatile instance;	  // 同时要保证 不缓存
	Singleton() { cout << "Singleton()" << endl; }	
	~Singleton() { cout << "~Singleton()" << endl; }


	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
};

Singleton * volatile Singleton::instance=nullptr;

int main()
{
	Singleton* p1 = Singleton::getInstance(); 
	Singleton* p2 = Singleton::getInstance(); 
	Singleton* p3 = Singleton::getInstance();


	cout << p1 << endl;
	cout << p2 << endl;
	cout << p3 << endl;

	Singleton::destroyInstance();

	return 0;
}

instance加一个volatile,这是给指针加的(不是给指针的指向加的)。好处是当一个线程对instance赋值时,其他线程马上均能看到instance的改变。因为线程现在已经不对共享变量进行缓存了,大家看的都是其原始内存中的值

非互斥锁的线程安全懒汉式单例模式

#include <iostream>
#include <thread>
#include <list>
#include <mutex>
#include <queue>
#include <condition_variable>
using namespace std;

std::mutex mtx;
class Singleton
{
public:
	
	static Singleton* getInstance() 
	{
		static Singleton instance;
		return &instance; 
	}


private:
		
	Singleton() { cout << "Singleton()" << endl; }	
	~Singleton() { cout << "~Singleton()" << endl; }


	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
};


int main()
{
	Singleton* p1 = Singleton::getInstance(); 
	Singleton* p2 = Singleton::getInstance(); 
	Singleton* p3 = Singleton::getInstance();


	cout << p1 << endl;
	cout << p2 << endl;
	cout << p3 << endl;


	return 0;
}

函数静态局部变量的初始化在汇编指令上已经自动添加线程互斥指令了,也就是已经线程安全了!(linux下 gdb调试, 看底层汇编, 看老师博客) vs里看汇编是看不到的

在 C++11 及以后的标准中,局部静态变量的初始化线程安全的

对于static静态局部变量的初始化,编译器会自动对它的初始化进行加锁和解锁控制,使静态局部变量的初始化成为线程安全的操作,不用担心多个线程都会初始化静态局部变量,因此上面的懒汉单例模式是线程安全的单例模式!

三、工厂模式(创建型模式)

主要用于 创建对象 非常多的 时候

先了解一下工厂模式

工厂模式是一个比较常用的创建型设计模式,主要是封装了对象的创建,其中可以细分为三种:简单工厂模式(Simple Factory)、工厂方法模式(Factory Method)、抽象工厂模式(Abstract Factory)

3.1 简单工厂模式

简单工厂模式:

  • 定义:简单工厂模式只有一个工厂类,通过该工厂类的静态方法来创建具体的产品对象。工厂类根据不同的参数决定实例化哪一个产品类(简单工厂模式不属于标准OOP设计模式中的一项)
  • 适用场景:适用于产品种类较少且不经常扩展的场合。可以快速创建不同的产品对象,但扩展性有限

代码核心:
枚举类和简单工厂类

#include <iostream>
#include <memory>
using namespace std;

class Car
{
public:
	Car(string name) : _name(name) {}
	virtual void show() = 0;
protected:
	string _name;
};

class Bmw : public Car
{
public:
	Bmw(string name) : Car(name) {}
	void show() override { cout << "我是宝马:" << _name << endl; }
};

class Audi : public Car
{
public:
	Audi(string name) : Car(name) {}
	void show() override { cout << "我是奥迪:" << _name << endl; }
};

enum CarType  // 枚举值
{
	BMW, AUDI
};

class SimpleFactory   // 简单工厂的 核心
{
public:
	static Car* createCar(CarType ct)
	{
		switch (ct)
		{
		case BMW:
			return new Bmw("X3");
		case AUDI:
			return new Audi("A8");
		default:
			cerr << "传入参数不正确" << ct<< endl;
			return nullptr;
		}
	}
};

int main()
{
	// 买车, 不需要知道车是怎么构造的
	//Car* p1 = new Bmw("Bmw X1"); //使用的时候, 需要记住 很多 派生类名

	// Car* p1 = SimpleFactory::createCar(BMW);  
	// Car* p2 = SimpleFactory::createCar(AUDI);
	// if (p1) p1->show();
	// if (p2) p2->show();
	// delete p1;
	// delete p2;

	// 改成智能指针,自动释放资源, 注意需要头文件memory
	unique_ptr<Car> p1(SimpleFactory::createCar(BMW));
	unique_ptr<Car> p2(SimpleFactory::createCar(AUDI));
	if (p1) p1->show();
	if (p2) p2->show();

	return 0;
}

现在,简单工厂模式就实现了,但是这种方法有一些缺陷,因为同一个工厂不会又造宝马又造奥迪,又造手机又造键盘的,这一般不符合实际的情况,而且最重要的是这个工厂不符合“开闭原则”,如果我们要增删车辆,就要修改接口,这样很不好。

基于这样的缺陷,接下来看看工厂方法模式

3.2 工厂方法模式

工厂方法模式:

  • 定义:工厂方法模式由一个抽象工厂类和多个具体工厂类组成。每个具体工厂类负责创建一个具体产品对象。通过继承抽象工厂类的方法,可以多态地创建不同的具体产品
  • 适用场景:适用于某个产品种类中包含多个具体产品的情况。能够通过增加新的工厂类来扩展产品系列

代码核心: 工厂方法类(虚构造函数)及其派生类

#include <iostream>
#include <memory>
using namespace std;

class Car
{
public:
	Car(string name) : _name(name) {}
	virtual void show() = 0;
protected:
	string _name;
};

class Bmw : public Car
{
public:
	Bmw(string name) : Car(name) {}
	void show() override { cout << "我是宝马:" << _name << endl; }
};

class Audi : public Car
{
public:
	Audi(string name) : Car(name) {}
	void show() override { cout << "我是奥迪:" << _name << endl; }
};

// 工厂方法
class Factory
{
public:
	virtual Car* createCar(string name) = 0; // 工厂方法
};
// 宝马工厂
class BMWFactory : public Factory
{
public:
	Car* createCar(string name) override { return new Bmw(name); }
};
// 奥迪工厂
class AudiFactory : public Factory
{
public:
	Car* createCar(string name) override { return new Audi(name); }
};

int main()
{
	// 这个构建有点麻烦, 慢慢看, 先创建相应的工厂, 在使用工厂创建相应的产品

	//unique_ptr<Factory> bmwfty(new BMWFactory());
	unique_ptr<Factory> bmwfty = make_unique<BMWFactory>() ; // c++14 new在外面是不行的
	unique_ptr<Factory> audifty(new AudiFactory());

	unique_ptr<Car> p1(bmwfty->createCar("X6"));
	unique_ptr<Car> p2(audifty->createCar("A8"));
	p1->show();
	p2->show();

	return 0;
}

现在符合“开闭原则”,如果现在想加一个奔驰工厂,直接增加BenzFactory类就行了

3.3 抽象工厂模式

抽象工厂模式:

  • 定义:抽象工厂模式由一个抽象工厂类和多个具体工厂类组成,同时有多个产品接口或抽象类。每个具体工厂类可以创建一组相关的产品对象
  • 适用场景:适用于涉及多个产品种类的复杂系统,可以创建一组相关或依赖的产品对象。适合于需要创建多个系列产品的情况

工厂方法与抽象工厂区别:工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。 工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。因此,工厂方法模式可以被视为抽象工厂模式的一个特例或简化形式

当一个工厂不止生产车, 还生产对应的灯, 即一组产品

还有缺点: 基类添加新产品时, 派生类也得加

#include <iostream>
#include <memory>
using namespace std;

class Car {
public:
	virtual void show() = 0;
};

class Light {
public:
	virtual void type() = 0;
};
// ===================================================
class BMWCar : public Car {
public:
	void show() override { cout << "BMW car" << endl; }
};

class BMWLight : public Light {
public:
	void type() override { cout << "BMW Light" << endl; }
};
// ===================================================
class AudiCar : public Car {
public:
	void show() override { cout << "Audi car" << endl; }
};

class AudiLight : public Light {
public:
	void type() override { cout << "Audi Light" << endl; }
};
// ===================================================
class AbstractFactory {
public:	// 产品簇
	virtual Car* createCar() = 0;
	virtual Light* createLight() = 0;
};

class BMWFactory : public AbstractFactory {
public:
	Car* createCar() override { return new BMWCar(); }
	Light* createLight() override { return new BMWLight(); }
};

class AudiFactory : public AbstractFactory {
public:
	Car* createCar() override { return new AudiCar(); }
	Light* createLight() override { return new AudiLight(); }
};
// ===================================================
int main()
{
	// 创建具体工厂
	unique_ptr<AbstractFactory> bmwfty(new BMWFactory());
	unique_ptr<AbstractFactory> audifty(new AudiFactory());

	// 使用工厂创建产品
	unique_ptr<Car> bmwCar(bmwfty->createCar());
	unique_ptr<Light> bmwLight(bmwfty->createLight());
	unique_ptr<Car> audiCar(audifty->createCar());
	unique_ptr<Light> audiLight(audifty->createLight());
	if (bmwCar) bmwCar->show();
	if (bmwLight) bmwLight->type();
	if (audiCar) audiCar->show();
	if (audiLight) audiLight->type();

	return 0;
}

工厂模式总结–特点

简单工厂 (Simple Factory)

  • 把对象的创建封装在一个接口函数里面,通过传入不同的标识,返回创建的对象。
  • 客户不用自己负责 new 对象,不用了解对象创建的详细过程。
  • 提供创建对象实例的接口函数不闭合,不能对修改关闭

工厂方法 (Factory Method)

  • Factory 基类,提供了一个纯虚函数(创建产品),定义派生类(具体产品的工厂)负责创建对应的产品。
  • 可以做到不同的产品,在不同的工厂里面创建,能够对现有工厂,以及产品的修改关闭。
  • 实际上,很多产品是有关联关系的,属于一个产品簇,不应该放在不同的工厂里面去创建,这样:
    • 一是不符合实际的产品对象创建逻辑。
    • 二是工厂类太多了,不好维护。

抽象工厂 (Abstract Factory)

  • 把有关联关系的,属于一个产品簇的所有产品创建的接口函数,放在一个抽象工厂里面 AbstractFactory
  • 派生类(具体产品的工厂)应该负责创建该产品簇里面所有的产品。

相关文章:

  • 知识库已上线
  • 同步双写与删缓存在缓存一致性的实践对比
  • linux---------进程概念(上)
  • Linux内核,内存分布
  • Python与数据库
  • ubuntu虚拟机上定制文件系统
  • AI Agent开发大全第六课-AI对话界面参数全解析
  • 解锁二叉树:高效存储与搜索的秘密武器
  • 物化视图详解:数据库性能优化的利器
  • Vs code搭建uniapp-vue项目
  • 【Linux网络-五种IO模型与阻塞IO】
  • 23种设计模式-生成器(Builder)设计模式
  • k8s kubernetes dashboard一直CarshLoopBackoff
  • 【强化学习】重要性采样(Importing Sample)
  • uniapp从 vue2 项目迁移到 vue3流程
  • 计算机二级web易错点(6)-选择题
  • 分库分表后,跨库查询和分布式事务解决方案
  • 详解内联容器标签<span>的用法
  • TruPlasma MF 7000 7150 (G2)软件
  • 《需求工程实战指南:从理论到避坑,附大创项目案例》
  • 遭车祸罹难的村医遇“身份”难题:镇卫生院否认劳动关系,家属上诉后二审将开庭
  • 新版城市规划体检评估解读:把城市安全韧性摆在更加突出位置
  • 夜读丨母亲为燕子打开家门
  • 最高人民法院、中国证监会联合发布《关于严格公正执法司法 服务保障资本市场高质量发展的指导意见》
  • 夜读丨读《汉书》一得
  • 制造四十余年血腥冲突后,库尔德工人党为何自行解散?