woaidongmao

文章均收录自他人博客,但不喜标题前加-[转贴],因其丑陋,见谅!~
随笔 - 1469, 文章 - 0, 评论 - 661, 引用 - 0
数据加载中……

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

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

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


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


1.
由于不能从构造函数传入allocator,那么每种类型的allocator必须是全局唯一的(Singleton)。无论SGI 的内存池(称为PoolAlloc),还是简单的new wrapper(称为NewAlloc)都只从一个地方(region)搞到内存,这大大限制了其使用。


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),但现在它无疑就是个累赘。


allocator
就跟 IOStream Locale/Facetauto_ptr valarray 一样,成为C++标准一直要背负的历史包袱。

有关问题:

> allocator是个类模板啊... ...其实例化要依赖于容器所关联的数据类型啊,怎么从构造函数传入?

allocator
一开始就不应该是个类模板,它只要提供void* alloc(size_t num_bytes) void free(void*) 这两个功能,就能让容器正确实现。毕竟在C++里,分配内存、构造对象、析构对象、释放内存这四步是可以独立开的,allocator 只要管好第1步和第4步就好了。

就算allocator是个未知类型,它也可以从构造函数传入,而无需作为容器本身的模板参数。同样的技术在shared_ptr里用过,Scott Meyers 也总结过: http://www.artima.com/cppsource/top_cpp_aha_moments.html (第5条)

>
而且何以有所谓 allocatorsingleton...?
每个容器有一个allocator的实例,从这个意义上讲,allocator不是Singleton。但是,由于不能从容器的构造函数传入allocator,那么容器在实例化allocator时只能用默认构造函数。这等于是让容器自己持有的allocator变成一个转发器,要么转为调用全局的malloc/free,要么转为调用某个全局的allocator(比如 SGI 的 内存池)。从这个意义上讲,每种*类型*allocator(不是每个allocator实体)只能从一块地方分配内存,所以是个Singleton


>
如果同一个实例化的容器类的不同对象如果使用不同的allocator,如何保证swap的有效和正确?

shared_ptr
是如何做到的?shared_ptr 能从构造函数接受DeleterAlloctor,而且实现了swap()

> 但还需探讨一下:我们能否改进allocator的设计?究竟这个问题是mission impossible,或者只是Stepanov没有找到最佳的设计而已?

很难说。自然,我们可以把allocator变成容器的成员而不是模板类型参数。

或者干脆把allocator定义成一个abstract base class,然后在容器里放allocator的指针(这么做肯定在标准委员会通不过,那帮人有虚函数恐惧症)。但是这样做了之后,马上遇到一个问题:allocator的生命期谁来控制,是不是在容器析构的时候同时析构allocator?如果这样,一个allocator就只能给一个容器用。如果不析构,那么allocator又由谁来释放呢?

或者按照shared_ptr的做法,allocator是未知类型的,具有值语义,但不必作为容器的模板参数(allocator实际上是容器的成员,但是其类型及大小未知,所以用了一个trick),这样生命期的问题倒是解决了。只不过allocator没办法内联,(甚至要通过虚函数来调用),很可能会被标准委员会拍死。

另外一个更为重要的问题,allocator没有传递性。比如vector<string> vs,如果 vs 用了我的MyAlloc,我希望它持有的string也用MyAlloc了分配内存,这样我对vs对象涉及的全部内存管理都能有所控制。但是很可惜除了自己写vector,没办法把MyAlloc传递给vector里的string。(如果把MyAlloc作为string类型的模板参数,就又回到原来的老路上去了。)

posted on 2011-03-29 10:16 肥仔 阅读(1804) 评论(5)  编辑 收藏 引用 所属分类: Boost & STL

评论

# re: C++ 标准库中的 allocator 是多余的[未登录]  回复  更多评论   

allocator的确不尽人意,也确实像你说的那样Singleton,难道malloc/free和new/delete就不Singleton,我看未必吧。管理内存是件很复杂的事情,无论allocator还是malloc/free和new/delete无非都是一wrapper而已。如果CPU个数多了,它们都会死掉,甚至PC系统内存管理相关的API都会死掉,因此你不应该指责allocator。如果想设计一个高并发获得很好加速比,有效防止blowup和false sharing,且低的overhead,那是一件很困难的事情,目前没有一个这么绝对理想的。我不愿意听什么大牛二牛,我只知道Win2008和所有耳闻的Unix版本目前都做不到这么理想。

STL容器之所以有个Alloc,是希望用户在必要时自定义allocator,不同的allocator怎么可能互换?一个进程内不同的内存地址还能互换吗?这岂不荒唐,即使允许重新映射的别的物理地址,那是操作系统的事情,allocator能有这本事岂不笑谈,那还要操作系统虚拟内存管理器干啥?

shared_ptr有啥好的?除了performance问题和更大的内存overhead,还能有啥好处?但是无可厚非,在速度和内存要求不高的场合,或者数据量很小时,用它也未尝不可。我们总得尊重人权吧!
2011-08-18 22:58 | Chipset

# re: C++ 标准库中的 allocator 是多余的  回复  更多评论   

硬伤:不能从构造函数传入allocator。

自己看下能不能传。
2012-05-02 12:22 | nous

# re: C++ 标准库中的 allocator 是多余的  回复  更多评论   

怎么加关注啊
2013-01-08 13:20 | hxs

# re: C++ 标准库中的 allocator 是多余的  回复  更多评论   

stl容器在原则上是支持stateful allocator的,它调用allocate()是通过点算符而不是::算符,这样每个对象都有自己的allocator,虽然类型上是一样的。
2013-05-15 03:51 | wxy

# re: C++ 标准库中的 allocator 是多余的  回复  更多评论   

这种材料的主题是非常有趣的,令人惊异的是看到了明显的交谈here.Keep了出色的执行,这要感谢提出建议,这些信息
2014-01-09 11:58 | essays

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理