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

C++ 标准库中的allocator是多余的

C++ 标准库中的allocator是多余的

我认为C++的allocator是依赖注入的一次失败的尝试。

C/C++里的内存分配和释放是个重要的事情,我同意,在写library的时候,除了默认使用malloc/free,还应该允许用户指定使用内存分配的函数。用现在的话说,如果library依赖于内存分配与释放,就应该允许用户注入这种依赖。我看到有些C library是支持这个的,可以在初始化时传入两个函数指针,指向内存分配和释放的函数。

问题是,allocator是模板参数,而不是构造函数的参数。这意味着

1. 由于不能从构造函数传入allocator,那么每种类型的allocator必须是全局唯一的(Singleton)。无论SGI 的内存池(称为PoolAlloc),还是简单的new wrapper(称为NewAlloc)都只从一个地方(region)搞到内存,这大大限制了其使用。(补注:这是 SGI STL 的限制,标准 C++ 运行从构造函数传入 allocator,后面的论述依然成立。)


2. allocator是vector类型的一部分,vector<string, PoolAlloc> 和 vector<string, NewAlloc> 是两个类型,不可相互替换。这不仅暴露了实现,还暴露到了类型上,恐怕没有比这更糟糕的了。

下面举例说明,

对于1,假设我有一个任务(假设是parse),需要分配很多小块内存,总容量不超过20M。为了防止内存泄露及避免内存碎片,我希望在任务开始时,先从系统拿到20M内存,供这个任务使用(parse里分配内存只需要改一个指针,释放内存是空操作),等任务完成后,我一次性释放这20M内存,这样既高效又安全。然而C++的allocator并不能帮我实现这一点,因为它是全局的。我不能替换全局的allocator,因为那会影响其他线程。也不能在运行时指定某个vector<string>用哪种allocator,因为类型是编译时确定的。

对于2,如果我想写一个普通的以vector<string>为参数的函数,这不难


void process(vector<string>& records);

由于vector<string, PoolAlloc>和vector<string, NewAlloc>类型不同,process只能接受一种。
但这完全没道理,我不过想访问一个vector<string>,根本不关心它的内存是怎么分配的,却被什么鬼东西allocator挡在了门外。


我要么提供重载:
void process(vector<string, NewAlloc>& records);
void process(vector<string, PoolAlloc>& records);

要么改写成模板:
template<typename Alloc>
void process(vector<string, Alloc>& records);

//(同理可知,如果在一个程序里使用多种allocator,那么所有涉及标准库容器的用户函数都必须改写为函数模板)

无论哪种"解决办法"都会导致代码膨胀,而且给标准库的使用者带来完全不必要的负担。

更糟糕的是,allocator给程序库的作者也带来了不必要的负担。如果想把process(vector<string>& records)放到某个library中,那么为了适应不同的allocator,必须把函数定义放在头文件里(因为这是个函数模板)。明明是针对一个固定类型(vector of string)的函数,却不得不写成函数模板,把实现细节暴露在头文件里,让每个用户都去编译一遍,这真是完全没道理。

根据以上的分析,基本上不可能在一个程序里混用多种allocator,既然一个程序只能有一种allocator,那为什么还要放到每个容器的模板参数里呢?提供一个全局的钩子不就行了嘛?

相反,shared_ptr就只有一个模板参数T,而他同样可以指定allocator----在构造时传入。

现在看来,vector(以及其他标准库容器)与其增加一个Alloc模板参数,不如在构造时传入两个函数指针,一个allocate,一个deallocate,定制的效果也一样。只不过这么做会让标准委员会的人觉得不够GP,很可能被拍掉。

总而言之,allocator并不能达到精确控制(定制)内存分配释放的目的,虽然它名以上是为此而生的。虽然在历史上可能有过正面效果(封装far / near pointer),但现在它无疑就是个累赘。就跟 IOStream 的 Locale/Facet ,auto_ptr 和 valarray 一样,成为C++标准一直要背负的历史包袱。


相关文章:

  • 数据结构与算法[LeetCode]—Linked List Cycle 确定单链表是否有环,并找出第一个环结点
  • 啊,目标!
  • 数据结构与算法[LeetCode]—数组中出现次数异与其他数的一个数
  • Linux 的DNS 的配置...
  • 我与网管师职业认证的钦定缘分
  • linux下显示网卡设备及驱动信息intel shell脚本
  • 数据结构与算法[LeetCode]—两个有序数组合并及找中点问题
  • 无法在web服务器上启动调试, Server Application Error......错误解决方法
  • 数据结构与算法[LeetCode]——sqrt(x)
  • RedHat 9 Linux SendMail 的配置
  • KMP算法深度解析
  • VS2005中Nebula3数据类型的调试信息显示
  • Extjs的ajax同步请求时post方式参数发送方式
  • 史上最“牛”的大型交换机配置图书
  • 数据结构与算法[LeetCode]—Maximum Subarray
  • 【node学习】协程
  • 8年软件测试工程师感悟——写给还在迷茫中的朋友
  • ES6系列(二)变量的解构赋值
  • exif信息对照
  • export和import的用法总结
  • Git初体验
  • java 多线程基础, 我觉得还是有必要看看的
  • JavaScript标准库系列——Math对象和Date对象(二)
  • JAVA多线程机制解析-volatilesynchronized
  • Node.js 新计划:使用 V8 snapshot 将启动速度提升 8 倍
  • redis学习笔记(三):列表、集合、有序集合
  • Vue.js源码(2):初探List Rendering
  • 短视频宝贝=慢?阿里巴巴工程师这样秒开短视频
  • 前嗅ForeSpider中数据浏览界面介绍
  • 使用parted解决大于2T的磁盘分区
  • 3月7日云栖精选夜读 | RSA 2019安全大会:企业资产管理成行业新风向标,云上安全占绝对优势 ...
  • 正则表达式-基础知识Review
  • ​LeetCode解法汇总2808. 使循环数组所有元素相等的最少秒数
  • #考研#计算机文化知识1(局域网及网络互联)
  • $refs 、$nextTic、动态组件、name的使用
  • (10)Linux冯诺依曼结构操作系统的再次理解
  • (9)STL算法之逆转旋转
  • (三)mysql_MYSQL(三)
  • (转)大型网站架构演变和知识体系
  • (转)一些感悟
  • ./configure、make、make install 命令
  • .bat批处理(十):从路径字符串中截取盘符、文件名、后缀名等信息
  • .bat批处理(十一):替换字符串中包含百分号%的子串
  • .cn根服务器被攻击之后
  • .NET delegate 委托 、 Event 事件
  • /*在DataTable中更新、删除数据*/
  • @JoinTable会自动删除关联表的数据
  • @SentinelResource详解
  • []Telit UC864E 拨号上网
  • [Android Studio 权威教程]断点调试和高级调试
  • [autojs]逍遥模拟器和vscode对接
  • [caffe(二)]Python加载训练caffe模型并进行测试1
  • [DAU-FI Net开源 | Dual Attention UNet+特征融合+Sobel和Canny等算子解决语义分割痛点]
  • [hdu 4552] 怪盗基德的挑战书
  • [InnoDB系列] -- SHOW INNODB STATUS 探秘