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

C++ Pimpl(Pointer to Implementation)设计思想(转载)

C++ Pimpl(Pointer to Implementation)设计思想

背景

最近都在写C++库,可以说是库库地写库。
只能说这库得写啊,不写不行。真有了下游使用方,才能真正理解很多工程实践。不然就只知其然,而不知其所以然。本文的灵感就由此而来,大家就看个乐呵,要是一不小心真学了点儿东西,嘿,那血赚。
本文使用的代码保存在这个repo里:
https://gitee.com/fataswellassad/clean-header
下载的时候建议使用命令:

git clone --recurse-submodules https://gitee.com/fataswellassad/clean-header.git

因为这样可以把子模块一起clone下来,比较省事喔。这个repo的使用方法都在README里了,就不多赘述了。欢迎大家扒下来自己build跑跑。
我们选取的例子是一个自然语言处理和大语言模型中都必不可少的组件:分词器(tokenizer)。
它有三个核心功能:

  1. 读入预先训练好的分词器文件。一般叫xxx.model。
  2. 编码(encode):输入一个字符串(string),输出一串数字(vector),对应着组成这个字符串的token们。
  3. 解码(decode):输入一串数字,输出一个字符串。即encode的逆操作

多说无益,我们直接看这个东西的使用范例便是,以下内容来自main.app,及其运行输出。
在这里插入图片描述在这里插入图片描述
可以看到encode再decode,原文就回来了。和咱们预期的一样。

那么这么酷炫的功能是怎么整出来的呢。当然是通过调包儿,总不能是我自己搓出来的吧。不过这调包也是小有说法的。我们先来检阅一下最朴素的实现(位于repo的vanilla分支):

tokenizer.hpp
在这里插入图片描述
tokenizer.cpp
在这里插入图片描述
CMakeLists.txt
在这里插入图片描述
这个实现方案的优点我们已经看到了,能build,能跑。不过也就到此为止了。
我们来列举一下缺点。所有缺点的根源都在于:
#include<sentencepiece_processor.h>出现在了tokenizer.hpp这个头文件里!

  1. 下游用户拿到你build好的.so,和头文件.hpp,乐呵呵想用来build自己的东西。然后就发现:
    在这里插入图片描述2. 你猥琐调包儿的真相在hpp文件里一目了然。到时候你财务出身的老板和客户说:“啊,对,百分百纯!自!研!”然后对面对接的技术人员一拿到头文件,你猜他能不能乐出来。

这有问题,咱得解决。咋解决呢,核心思想是要把那个include给搬到tokenizer.cpp文件里雪藏起来。但是那样的话,SentencePieceProcessor sp作为一个成员变量,当场就会死给你看。成员函数可以拿到cpp文件里来实现,但是成员变量不可以,因为C/C++要求任何一个struct/class的大小,都要能在编译时确定。那怎么办,阿巴阿巴…

不要慌!编程的时候要秉承一个信念,越是容易遇到的问题,越有成熟的解决方案。事实上,这个问题的历史几乎和C/C++语言的历史一样久远。解决方案不仅有,而且还有两种。一个就是著名的pImpl idiom (Pointer to IMPLementation),另一个是借助抽象类和工厂函数。以下分别介绍。

pImpl Idiom

本实现请见repo中的pimpl分支。

tokenizer.hpp
在这里插入图片描述
tokenizer.cpp
在这里插入图片描述
CMakeLists.txt改动细微,只是把那个include_dir去掉了,毕竟已经不需要了。main.cpp更是完全不用更改。

需要讲解的主要有两点。

  • 我们通过提前声明引入了一个TokenizerImpl类,然后把指向它的一个指针(unique_ptr)放进了Tokenizer类里。之后在cpp文件里,我们把原先Tokenizer的工作都代理给了TokenizerImpl。
  • 一切实现都必须在cpp文件中进行,包括析构函数。不然仅有提前声明,C++默认生成的析构函数不知道TokenizerImpl的size,无法进行析构。

抽象基类 + 工厂函数

tokenizer.hpp
在这里插入图片描述
tokenizer.cpp
在这里插入图片描述
main.cpp (与pimpl不同,这个会改变接口)
在这里插入图片描述
我们实际上利用的是C++多态的特性。create_tokenizer这个工厂函数返回的是一个指针(unique_ptr),因为多态只对指针或引用生效。真正干活的TokenizerImpl是这个指针所指对象的运行时类型,从头文件是无法感知到的。

这和我们的需求不谋而合:你就知道这东西能decode/encode就行了,别的不需要管

总结

我们可以比较这两种实现的异同/优劣。

相同:

  • 都能完成对类实现细节的封装,保证一个清爽纯净的头文件。
  • 都要求进行堆内存的分配。这个应该没有办法解决,因为我们的需求是把成员变量藏起来,但C++编译器需要从头文件里读出sizeof(Tokenizer)才能把它放在栈上。

不同:

  • pImpl idiom能保证使用侧用法与封装前完全相同。这对于重构某些祖传代码是十分重要的。这点上远胜抽象类。
  • 如果我们本来就有几种不同的Tokenizer,比如现在这个是sentencepiece的,旁边还有一个Huggingface的,那抽象基类就变成了一个很自然的方案,工厂函数的逻辑也可以完美复用。这点,抽象类胜。

参考文献

C++小寄巧-10:儒雅随和头文件
PImpl
C++ Pimpl(Pointer to Implementation)设计思想
Pimpl(Pointer to Implementation)模式详解

http://www.dtcms.com/a/412284.html

相关文章:

  • uthash.h库源码
  • 网站建设 美食站点网页传奇哪个比较好玩
  • 创建一个新的公司网站南海桂城城乡建设局官方网站
  • 9.27 枚举|前缀和dp|正则shell
  • 卫朋:IPD流程落地-技术管理实战
  • 兽装定制网站wordpress站点登陆
  • 网站建设学习哪家专业中龙建电力建设股份有限公司网站
  • 百度获取入口哈尔滨网站优化流程
  • 装修公司免费网站模版防城港北京网站建设
  • 实现私有 yum 仓库搭建
  • 用于批量上传本地 Git 仓库到 Gitcode 的组织仓库
  • HCIP-IoT 真题详解(章节D),嵌入式基础与南向开发 /Part3
  • 新媒体公司网站怎么做海淘返利网站怎么做
  • 软件源码购买一般在哪个网站2019还有人做网站淘宝客吗
  • 网站排名如何做广州网站设计工作室
  • JavaScript学习笔记(十三):JS解构技巧详解
  • 自己做服务器和网站网络应用程序方案设计
  • 百度Feed实时数仓架构升级
  • 韩都衣舍网站建设方案重庆模板网站哪个好
  • 公司网页网站建设 ppt网站建设费科目
  • 做网站主要学什么软件网站域名绑定破解
  • 苹果群控系统如何同时操作多个游戏账号?
  • webpack 中的tapable用法
  • 网站技术解决方案是什么有哪些建设工程类网站
  • 自适应平台(Adaptive Platform)——Specification of State Management
  • 微信 网站设计模板免费申请空间网站
  • Radan钣金CAM解决方案——小型智能工厂应用案例
  • 做网站的目的和意义长沙网站设计工作室
  • 力扣HOT100-跳跃游戏II
  • 秦皇岛网站排名公司网站被iframe