组织代码的方式
如果项目非常小且简单,或者明确知道将来不会需要额外的维护和功能扩展,那么面向过程可能是一个更快且成本更低的方案。
项目需求和特定场景。有些项目可能更适合面向过程的开发模式,比如一些计算密集型的科学计算软件,
在大型项目开发中,面向对象的方法支持更好的代码组织 使用面向对象的设计模式 以进一步改善项目架构,使得代码更加模块化,易于更改和扩展
面向对象开发则将问题分解为对象,并通过对象之间的交互来解决问题。
设计模式
创建型模型-解耦对象的创建与使用
工厂模式: 包括简单工厂、工厂方法、抽象工厂这3种细分模式。
单例模式(Singleton Pattern)
建造者模式(组合) 当参数过多的时候我们可以考虑使用建造者模式
SparkSession 可以通过建造者模式创建
原型模式 原型模式(Prototype)是一种创建型设计模式, 使你能够复制已有对象, 而又无需使代码依赖它们所属的类
工厂模式
工厂模式
不再通过直接使用new的方式来创建对象,通过提供一个通用的方法或类来获取对象
没有反射的情况下,工厂模式,基本都会得到那种用switch来做的方案,有点像这样:
工厂模式加宏
借助宏来实现自动注册
// 定义工厂方法
#define DECLARE_FACTORY_METHOD(ClassName)\
void* Create##ClassName() { return new ClassName(); }
## 使用
DECLARE_FACTORY_METHOD(A) // 对应的宏展开就是 void* CreateA() { return new A(); }
在派生类中的调用宏来注册自己
注册器和工厂模式
引入注册器的工厂模式
注册器是一种用于管理和维护各种工厂类的机制。通过注册器,我们可以在运行时动态注册新的工厂类,而无需修改已有的代码,从而实现真正的开闭原则。
注册器的核心思想
动态注册:允许在运行时动态添加新的工厂类。
解耦管理:工厂类的管理由注册器统一负责,业务逻辑无需关心具体的工厂类。
灵活扩展:新增产品时,只需注册新的工厂类,无需修改已有的工厂或业务代码。
实现步骤
定义抽象工厂接口。
实现具体工厂类。
创建注册器类,用于注册和获取工厂。
在业务逻辑中通过注册器获取工厂实例
直接注册实体类会导致一个问题:项目启动时会加载所有注册器中的类,无法实现按需加载
注册器模式来避免增加if-else的操作,
引入注册器机制后,工厂模式的扩展性和灵活性得到了显著提升,允许在运行时动态注册和获取工厂类,进一步遵循了开闭原则。
建造者模式
01.在C++编程中,getter和setter是面向对象编程中非常重要的概念。
它们提供了一种有效的方式来封装类的私有成员变量,并且可以控制对这些成员变量的访问和修改权限
02.构建者模式
构建者模式:
Builder: 为创建Product对象的各个部件指定抽象接口。
ConcreteBuilder: 实现Builder的接口以构造和装配该产品的各个部件,定义并明确它所创建的表示,并提供一个检索产品的接口。
Director: 构造一个使用Builer接口的对象。 -将builder构建的产品部件组装成产品,对外隐藏内部组装细节
Product: 表示被构造的复杂对象。
将配置从目标类中隔离出来,避免作为过多的setter方法
--- Builder模式比较常见的实现形式是通过链式调用 -- 复杂对象的 构建 与它的 表示 分离
代码的套路:操千曲而后晓声,观千剑而后识器
设计模式
设计模式---经验
第一类是创建型模式 ,该模式通常和 对象的创建有关,涉及到对象实例化的方式
单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式五种;
第二类是结构型模式,结构型模式描述的是如何组合类和对象来获得更大的结构
代理模式、装饰者模式、适配器模式、桥接模式、组合模式、外观模式、享元模式共7种模式。
第三种是行为型模式,用来描述对类或对象怎样交互和怎样分配职责
模板模式、命令模式、责任链模式、策略模式、中介者模式、
观察者模式、备忘录模式、访问者模式、状态模式、解释器模式、迭代器模式11种模式
其他
注册模式 是一种常见的设计模式,主要就是将多个实例注册到一个统一的注册器中,然后通过方法直接去调用需要的实例
注册器一般都是和工厂模式等一起使用,在初始化的时候,将实例注册进去
工厂模式
工厂模式通过抽象工厂接口将产品的创建逻辑分散到具体工厂类中,从而遵循了开闭原则,提高了系统的扩展性和可维护性。
原生C++
1. 入口函数-- main 函数所在的文件
主函数
2 函数中的类
01.编写程序接入点:
02. GER()
c++实现一个工厂模式,再通过宏定义可以轻松的实现注册类的功能
03.定义具体
04.client--入口函数调用
lambda 表达式
最简单的匿名函数是{},它没有参数也没有返回值
[](int a, int b) {return a + b;};
[] 是捕获列表,表示不捕获任何外部变量。(int a, int b) 是参数列表,表示接收两个整数参数。{ return a + b; } 是函数体,返回两个整数的和
个闭包. 闭包被定义在Lambda表达式声明中的方括号[]内. 这个机制允许这些变量被按值或按引用捕获. 捕获外部变量: 捕获外部变量的引用:
[=]() { return x + y;}; 捕获所有外部变量的副本(通过值捕获)
[&]() { x += 5; y += 5;}; 捕获所有外部变量的引用
如果没有参数,空的圆括号()可以省略.返回值也可以省略
许在定义无参数的lambda表达式时省略小括号。通过这个改变,使用lambda变得更加简洁
std::thread t([]() { std::cout << "Hello from thread!" << std::endl;});
宏定义
宏 macro
#define还可以用于条件编译 #undef指令可以取消已定义的宏
宏是一种强大的预处理工具,它允许程序员在编译之前对源代码进行文本替换或条件编译
对象宏(也称为宏常量)和函数宏
宏中定义一个函数对象(如std::function或自定义的函数对象) // 定义一个宏来创建一个函数对象
1.
#define REPLACE(className) \
className* create_##className{ \
return new className; \
} \
REPLACE(int) // 使用宏,此时就定义了一个函数,名为int* create_int();
宏还有一个很奇特的操作:
# 功能是将其后面的宏参数进行字符串化操作,简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号。
## 功能是在带参数的宏定义中将两个子串联接起来,从而形成一个新的子串。
2.lambda表达式的返回值可以由编译器推导,还可以通过包装器function来包装
01.包装器function 可以声明成下面的形式:
function<void*(void)>
function<void*()>
02.map就声明成下面的形式
std::map<std::string,std::function<void*(void)>>
03. 在实际场景中,右值引用和std::move被广泛用于在STL和自定义类中实现移动语义,避免拷贝,从而提升程序性能。
函数式编程
高阶函数、链式调用、惰性求值和柯理化等函数式编程特性
std::move std::forward
std::forward<T>(u)有两个参数:T与 u。 a. 当T为左值引用类型时,u将被转换为T类型的左值; b. 否则u将被转换为T类型右值。
#include<functional> std::function std::bind
#include<utility> std::forward std::move std::forward是C++11引入的函数模板,它的作用是实现函数参数的完美转发
Lambda 表达式
函数对象的定义和使用:函数对象是一个类或结构体,它重载了函数调用运算符"()"
#include <functional> 预定义的函数对象,如std::plus、std::less
std::function 是常用的函数适配器,本质是类模板 。
它能 存储、复制和调用任何可调用对象,为不同可调用对象提供统一调用接口,使用者无需关心其具体类型
std::bind也是一个函数模板,返回值是一个仿函数 ,是可调用对象
函数对象(也称为仿函数或functor)是一种重载了operator()的对象,这使得对象表现得像函数一样可以被调用
#include <memory> std::unique_ptr std::shared_ptr
#include <mutex> 互斥锁(Mutex)是一种同步机制,用于保护共享资源免受并发访问的影响
#include <shared_mutex> std::shared_mutex c++17
多线程编程
线程 读写锁 原子操作 异步
C++ 11之前: 操作系统平台提供的API,比如Linux的<pthread.h>,或者windows下的<windows.h> 。
C++11提供了语言层面上的多线程-原生线程库,包含在头文件<thread>中
std::thread是C++ 11提供的原生线程库,它简化了多线程编程,提供了线程创建、管理和同步等基本功能
多线程
#1.include<thread>
#include<thread>头文件中声明,因此使用 std::thread
2.异步编程
#include<future>
std::future :异步指向某个任务,然后通过future特性去获取任务函数的返回结果。
std::aysnc :异步运行某个任务函数。 std::promise :线程1设置了某个值,然后通知另外的线程
3.锁
#include<mutex>
std::mutex mtx; // 创建一个互斥锁 std::lock_guard
mtx.lock(); // 上锁
mtx.unlock(); // 解锁
使用 std::lock_guard 来自动管理锁的获取和释放
std::shared_mutex 支持多个读线程同时上锁,std::mutex不支持。
std::shared_mutex 写线程上锁后阻塞所有其他读写线程,std::mutex 只阻塞其他写线程。
std::shared_mutex 不支持递归上锁,std::mutex支持。
读写锁
#include <shared_mutex> // 对于读写锁,使用shared_mutex头文件和类型
读线程使用std::shared_lock 进行上锁,写线程使用 std::unique_lock进行上锁
#include <shared_mutex>
std::shared_mutex mutex;
// 读线程
void reader() {
std::shared_lock<std::shared_mutex> lock(mutex);
// 访问共享数据
}
// 写线程
void writer() {
std::unique_lock<std::shared_mutex> lock(mutex);
// 访问共享数据
}
4.原子操作
#include <atomic>
std::atomic 模板来创建一个原子对象
std::atomic<int32_t> lock_num_ = {0};
5. 条件变量
#include <condition_variable>
6.其他
C++ 中的 type traits 主要依赖于模板元编程。标准库中的 type traits 位于头文件 <type_traits>
std::chrono::duration:表示一段时间间隔
#include<algorithm>
std::sort(container.begin(), container.end(), compare_function);
std::find(container.begin(), container.end(), value);
std::for_each(v.begin(), v.end(), [](int &n){ n *= 2; });
#include <initializer_list>
std::initializer_list<int> ilist
<array> 、<deque>、<forward_list> 、<list>、<vector>
<unordered_map> <unordered_set>
进一步升级
01.编译时
02.在运行时根据用户的输入或配置文件来决定加载和使用哪些类
03.动态代码更新或热补丁,是指在程序运行过程中,无需停止程序,直接对程序的代码进行更新的技术
在 C++中,可以将需要更新的代码封装成动态链接库,当需要进行代码更新时,只需要替换动态链接库文件即可
C++ 嵌入脚本
插件机制是一种将程序的功能模块化,通过加载插件来扩展程序功能的方法
插件机制仍需要考虑的一些问题如错误处理,数据类型,版本控制,与框架代码以及应用代码的分离等等
Qt之类的框架,它们为C++实现了一套反射系统
参考
【项目四】C++实现反射机制(通过宏和包装Lambda表达式实现) 原文链接:https://blog.csdn.net/yyy11280335/article/details/128524632
按类别划分的标头 https://learn.microsoft.com/zh-cn/cpp/standard-library/cpp-standard-library-header-files
https://github.com/autonomousvision/navsim
https://github.com/OpenDriveLab/OpenScene/blob/main/DriveEngine/process_data/openscene_scenario_visualization.py
https://github.com/Tsinghua-MARS-Lab/Occ3D/blob/master/utils/vis_occ.py
设计模式学习(二)工厂模式——工厂方法模式+注册表 https://www.cnblogs.com/paw5zx/p/18229334