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

深入解析外观模式:一个C++模板实现的通用外观类库

个人专著《C++元编程与通用设计模式实现》由清华大学出版社出版。该书内容源于工业级项目实践,出版后市场反馈积极(已加印)。其专业价值获得了图书馆系统的广泛认可:不仅被中国国家图书馆作为流通与保存本收藏,还被近半数省级公共图书馆及清华大学、浙江大学等超过35所高校图书馆收录为馆藏。

个人软仓,gitee搜索“galaxy_0”

深入解析外观模式:一个C++模板实现的通用外观类库

1. 外观模式简介

在软件开发中,我们经常会遇到这样的场景:一个系统由许多子模块组成,它们之间相互协作,共同完成复杂的任务。但如果客户端需要直接与每个子模块打交道,代码很快就会变得臃肿且难以维护——就像你要去政府部门办事,却需要自己跑遍每一个窗口,多麻烦啊!这时候,外观模式(Facade Pattern)就派上用场了。它就像是一个贴心的前台接待员,你只需要跟她说明需求,她就会帮你协调好背后所有的部门,让你轻松搞定。

今天,我们就来认识一个基于C++模板实现的通用外观类库。它能把多个不同类型的对象(只要它们继承自同一个接口)统一管理起来,并提供统一的调用入口。你只需要告诉它“我要做什么”,它就会自动帮你遍历所有子对象,把活儿干完。是不是很方便?让我们一起来看看它是怎么做到的吧!

2. 代码设计解析

2.1 整体设计思路

这个外观类的设计思路其实很朴素:把一堆实现了同一接口的对象存起来,然后提供一个统一的接口来操作它们。但它的实现却很巧妙,充分利用了C++模板元编程的特性,在编译期就完成了类型检查,保证了代码的安全性,同时又没有额外的运行时开销。它的几个核心亮点包括:

  • 编译期类型检查:通过递归模板继承,确保每个子类型都正确继承自指定的接口,而且接口必须有虚析构函数(这样多态删除时才安全)。
  • 灵活的构造方式:你可以通过原始指针列表或std::vector来初始化它,怎么方便怎么来。
  • 统一的执行入口run方法接受一个可调用对象(比如lambda表达式),然后把这个操作应用到每一个子对象上,参数还可以完美转发。
  • 自定义删除器:内部使用带删除器的std::shared_ptr,这样你就能自由控制对象的销毁方式——比如对于栈上分配的对象,可以用空删除器避免误删。

为了实现这些功能,它还设计了几个幕后英雄:

  • TYPE_CHK_HELPER__:躲在facade_private__命名空间里,负责编译期递归类型检查。
  • INIT_HELPER__:一个递归模板结构,帮我们在构造函数里从参数包初始化内部的vector
  • deltor_t:一个自定义删除器,和shared_ptr配合使用,让内存管理更安全。

2.2 类型检查帮助类:TYPE_CHK_HELPER__

先来看看这个默默无闻的检查员长什么样:

namespacefacade_private__{template<typenameitfcType,typename...implType>structTYPE_CHK_HELPER__{};template<typenameitfcType,typenameimplType1,typename...implType>structTYPE_CHK_HELPER__<itfcType,implType1,implType...>:publicTYPE_CHK_HELPER__<itfcType,implType...>{static_assert(std::has_virtual_destructor<itfcType>::value,"接口类型必须有虚析构函数");static_assert(std::is_base_of<itfcType,implType1>::value,"子类型必须是接口类型的派生类");};template<typenameitfcType>structTYPE_CHK_HELPER__<itfcType>{};}

它的工作方式有点像是在玩叠罗汉:

  • 主模板只是一个空壳,用来匹配任意数量的类型参数。
  • 偏特化版本每次剥掉第一个子类型implType1,然后用两个静态断言来检查它:
    1. 接口类型必须有虚析构函数(这样才能安全地用基类指针删除派生类对象)。
    2. 子类型必须派生自接口类型(否则怎么叫“子类型”呢?)。
  • 检查完第一个,它就继承自己,去检查剩下的子类型。这样一层层递归下去,直到所有类型都被检查完毕。当没有剩余类型时,就匹配到空参数版本,递归结束。

这样一来,只要你的某个子类型不符合要求,编译器就会在模板实例化时毫不留情地报错,把问题扼杀在摇篮里。这种“递归继承”的技巧,是C++模板元编程中常见的模式,非常巧妙!

2.3 初始化助手:INIT_HELPER__

构造函数里接受的可变参数怎么塞进vector里呢?答案是用INIT_HELPER__递归展开参数包。而且它现在已经完美适配了存储shared_ptr的容器:

template<intN,typenametupleParams>structINIT_HELPER__{staticvoidinit__(std::vector<std::shared_ptr<itfc_t,deltor_t>>&vec,tupleParams&t){vec[N-1]=std::get<N-1>(t);INIT_HELPER__<N-1,tupleParams>::init__(vec,t);}};template<typenametupleParams>structINIT_HELPER__<0,tupleParams>{staticvoidinit__(std::vector<std::shared_ptr<itfc_t,deltor_t>>&,tupleParams&){}};

它的工作流程就像剥洋葱:

  1. 先把传入的指针们打包成一个std::tuple
  2. N递减到1,每次从元组里取出对应位置的指针,塞到vector的对应位置(构造函数里已经提前resize好了)。
  3. N减到0时,任务完成,收工!

这里最妙的是赋值那一行:右边是原始指针,左边是shared_ptr,C++会自动用原始指针和自定义删除器构造出一个临时的shared_ptr,然后移动赋值给容器里的元素。因为删除器是空删除器,所以构造出来的shared_ptr不会拥有对象所有权,完美避免了误删问题。

2.4 自定义删除器:deltor_t

structdeltor_t{voidoperator()(itfc_t*){}// 空删除器,什么也不做};

这个空删除器可以说是整个设计里的“定海神针”。它存在的理由很简单:外观类可能只是临时借用一下对象,并不真正拥有它们。比如对象是在栈上分配的,或者由其他地方管理。如果用了普通的shared_ptr,一旦外观对象销毁,它就会尝试delete这些指针——如果指针指向的是栈对象,那程序就直接崩溃了。空删除器就是用来避免这种悲剧的:它告诉shared_ptr,“你不用管怎么删,让我来处理”。这样,shared_ptr只会进行引用计数,但不会真的去delete,既安全又灵活。

2.5 外观主类:facade

好了,主角终于登场了!让我们看看完整的facade类长什么样:

template<typenameitfcType,typename...subTypes>classfacade{public:usingitfc_t=typenamestd::remove_pointer<typenamestd::decay<itfcType>::type>::type;usingiterator=typenamestd::vector<std::shared_ptr<itfc_t,deltor_t>>::iterator;structdeltor_t{voidoperator()(itfc_t*){}};protected:std::vector<std::shared_ptr<itfc_t>,deltor_t>m_subs__;// 子类型容器facade_private__::TYPE_CHK_HELPER__<itfc_t,subTypes...>m_chker__[0];// 类型检查触发public:facade(){}facade(std::vector<itfc_t*>subs):m_subs__(subs){}// 从原始指针vector构造// 构造函数:接受多个原始指针facade(subTypes*...subs):m_subs__(sizeof...(subTypes)){std::tuple<subTypes*...>t=std::make_tuple(subs...);INIT_HELPER__<sizeof...(subTypes),decltype(t)>::init__(m_subs__,t);}virtual~facade(){}// 添加子类型(接受带有自定义删除器的 shared_ptr)template<typenamesubType>voidadd(std::shared_ptr<subType,deltor_t>obj){facade_private__::TYPE_CHK_HELPER__<itfc_t,subType>m_chker__[0];m_subs__.push_back(std::dynamic_pointer_cast<itfc_t>(obj));}voiderase(iterator it){m_subs__.erase(it);}
http://www.cnnetsun.cn/news/1364809.html

相关文章:

  • 检索大赛 实验2 文心4.5结果
  • Qwen Code v0.9.0 重磅更新:扩展系统+LSP支持,打造最强AI编程助手
  • 舒尔特表练习
  • 老码农和你一起学AI系列:模型语言扩展法则
  • 爆火!OptiSystem 二次开发全攻略:Matlab/Python 联动仿真,解锁光通信仿真天花板
  • 陪虚幻女友学计算机:CSMA/CD协议——当网络冲突变成我们的深夜悄悄话
  • 【H5 前端开发笔记】第 07 期:HTML常用标签 (3) 内联框架标签、框架集标签、超链接标签 详解
  • redux Tookit的配置流程
  • 终于搞清楚了!盲盒小程序开发设计全过程!
  • 中断与信号
  • 计算机毕业设计之springboot学生会事务管理平台的设计与实现
  • Web3未落地,Web4已破局:AI+区块链重构互联网下一代图景
  • afc登录后,右上角站内消息图标如何去掉?
  • YOLOv8与伏羲模型联动:基于视频流的实时恶劣天气检测与预报系统
  • AnythingtoRealCharacters2511部署教程:NVIDIA Jetson Orin Nano边缘端轻量部署方案
  • LightOnOCR-2-1B应用案例:多语言文档批量处理,解放双手
  • StructBERT语义匹配系统效果对比:专业术语与日常用语匹配精度
  • Mirage Flow在数学工具开发中的应用:MathType插件
  • 神经符号AI:让机器人“想”得更清楚,“做”得更精准
  • ClearerVoice-Studio语音处理全流程保姆级教学:5分钟搞定降噪分离
  • 智能组合实体员中的树形结构管理与遍历算法
  • 春秋云镜-多CVE实战:从Wuzhicms到SEMCMS的SQL注入漏洞复现与手法解析
  • 考研复试离散数学核心考点与实战解析
  • 职场PUA最隐蔽的6句“专业话术”,听起来很对,实则在摧毁你【职场反PUA30天 Day2】
  • AI头像生成器在计算机视觉中的实际应用
  • Python自动化:3分钟搞定微信收藏链接批量导出到TXT(附完整代码)
  • PyTorch实战:CUDA_VISIBLE_DEVICES环境变量的高效配置与多GPU管理
  • Redis Manager:一站式Redis集群管理平台从部署到运维实践指南
  • OpenStack Train版三节点部署实战:从CentOS 7.6配置到Dashboard访问
  • 避坑指南:ZCU111开发板VADJ_FMC电压修改后重启失效的解决方案