STL可以说是C++的重要的组成部分,它提供了一系列方便使用的容器,可以供我们存放数据,节省了很多开发时间。不过如果打算在多个进程之间共享一个STL容器,却常常会遭遇一些困难。比如你正在使用一个map,一般的IPC方法,可能下意识的你就会选择使用共享内存,如果可以把这个map放入一块共享内存供多个进程操作,那该多方便呢。然而问题却没这么简单,由于STL帮我们完美的封装好了map的内部方法,包括内存分配方案,这也导致了我们没法把map直接简单的用placement new放置到已知的共享内存上。
要完成这个任务就需要我们自己实现一个基于共享内存的allocator,替换map默认的allocator,在这个allocator中实现map的内存分配方案。关于allocator的讲解可以看侯捷的《STL源码剖析》这本书,写一个简单的allocator直接套下边的模板就可以了,主要实现的是这几个函数:
-
max_size()容器的最大容量 -
allocate(num)为num个元素分配内存 -
construct(p)将p所指的元素初始化 -
destroy(p)销毁p所指向的元素 -
deallocate(p, num)收回p所指的num个空间
#include <limits>
#include <iostream>
namespace MyLib {
template <class T>
class MyAlloc {
public:
// type definitions
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
// rebind allocator to type W
template <class W>
struct rebind {
typedef MyAlloc<W> other;
};
// return address of values
pointer address (reference value) const {
return &value;
}
const_pointer address (const_reference value) const {
return &value;
}
/* constructors and destructor
* - nothing to do because the allocator has no state
*/
MyAlloc() throw() {
}
MyAlloc(const MyAlloc&) throw() {
}
template <class W>
MyAlloc (const MyAlloc<W>&) throw() {
}
~MyAlloc() throw() {
}
// return maximum number of elements that can be allocated
size_type max_size () const throw() {
return std::numeric_limits<std::size_t>::max() / sizeof(T);
}
// allocate but don't initialize num elements of type T
pointer allocate (size_type num, const void* = 0) {
// print message and allocate memory with global new
std::cerr << "allocate " << num << " element(s)"
<< " of size " << sizeof(T) << std::endl;
pointer ret = (pointer)(::operator new(num*sizeof(T)));
std::cerr << " allocated at: " << (void*)ret << std::endl;
return ret;
}
// initialize elements of allocated storage p with value value
void construct (pointer p, const T& value) {
// initialize memory with placement new
new((void*)p)T(value);
}
// destroy elements of initialized storage p
void destroy (pointer p) {
// destroy objects by calling their destructor
p->~T();
}
// deallocate storage p of deleted elements
void deallocate (pointer p, size_type num) {
// print message and deallocate memory with global delete
std::cerr << "deallocate " << num << " element(s)"
<< " of size " << sizeof(T)
<< " at: " << (void*)p << std::endl;
::operator delete((void*)p);
}
};
// return that all specializations of this allocator are interchangeable
template <class T1, class T2>
bool operator== (const MyAlloc<T1>&,
const MyAlloc<T2>&) throw() {
return true;
}
template <class T1, class T2>
bool operator!= (const MyAlloc<T1>&,
const MyAlloc<T2>&) throw() {
return false;
}
}
如果要使用这个allocator的话,可以这样:
#include <vector> #include "myalloc.hh" int main() { // create a vector, using MyAlloc<> as allocator std::vector<int,MyLib::MyAlloc<int> > v; // insert elements // - causes reallocations v.push_back(42); } |
不过写出这样一个简单的vector内存分配器是很容易,不过如果要针对map或者hash_map写一份高效鲁棒的内存分配器却也不是一件容易的事情。一个想法就是在共享内存上划分出内存池,自己对这块内存池进行维护,并且还得考虑进程同步问题。然而,一件幸运的事情是,在boost项目第1.35个版本之后,已经增加了一个叫做boost::interprocess的库,其中包含了关于多线程和多进程的很多有用的辅助类库,当中也有为STL容器所写的allocator,这里有一系列非常棒的示例程序。
不过其中有一个注意点,由于allocator是以类的形式提供的,所以当你在使用STL容器的时候,你需要保证allocator的实例没有被销毁,例如下边的函数希望产生mymap这个指针供其他函数使用,结果将是错误的。事实上,一旦该函数运行结束,mymap所指向的内存地址将会失效。
typedef int KeyType; typedef float MappedType; typedef std::pair<const KeyType, MappedType> ValueType; typedef allocator<ValueType, managed_shared_memory::segment_manager> ShmemAllocator; typedef map<KeyType, MappedType, std::less<KeyType>, ShmemAllocator> MyMap; MyMap* get_ptr(){ using namespace boost::interprocess; managed_shared_memory segment(create_only, "MySharedMemory", 65536); ShmemAllocator alloc_inst (segment.get_segment_manager()); //如果你希望把mymap当作参数传递到别的函数中去处理,请保证此时alloc_inst这个实例是存在的。 MyMap *mymap = segment.construct<MyMap>("MyMap")(std::less<KeyType>(),alloc_inst); return mymap; } |
另外由 managed_shared_memory 创建的共享内存默认属性是 600。总之,结合了STL和boost库,把STL容器放入共享内存中供多进程使用,就变成了一件非常容易而且愉快的事情。