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());
// If you wish to give `mymap' to other functions as parameters,
// please make sure the instance `alloc_inst' must be existed at
// that time.
MyMap *mymap = segment.construct<MyMap>("MyMap")(std::less<KeyType>(),alloc_inst);
return mymap;
}
另外由 managed_shared_memory
创建的共享内存默认属性是 600
。总之,结合了STL和boost库,把STL容器放入共享内存中供多进程使用,就变成了一件非常容易而且愉快的事情。