Boost的fast_pool_allocator(pool_allocator)没有右值引用版的construct函数

来源:互联网 发布:男儿行 酒徒 知乎 编辑:程序博客网 时间:2024/06/06 14:49

    因为工作需要的关系,在工程中使用了unordered_map,并在其中存储了一个自定义的类,这个类在构造时会初始化一个事件对象,基于C++03标准,一开始我并没有在析构函数中对事件对象进行销毁(因为STL内置容器是以复制构造函数初始化内部对象的,而其中会产生一些临时对象,而相比指针,内核对象在处理“复制”上更麻烦,要保证安全析构很麻烦),而是独立定义了close函数。这个设计运行很正常(只要没有忘记最后使用close函数就行)。

    随着工程的进行,想对上面这个设计做一下调整,一个是使用Boost.Pool库统一调整一下内存分配,这个调整很成功。第二个调整,原本想基于C++11标准的右值引用,对上面的类进行调整,停用不确定的复杂构造函数(C++03后个编译器对这个东西太多的调整和优化,使得很不确定,且效率低下),在析构函数中,释放对象,这一改问题出来了,程序运行出现了“死锁”。仔细查看和排除右值引用的使用,均没有发现问题,为此我还特地写了一个简化程序用于测试。

#include <unordered_map>#include <cstdio>using namespace std;class Test{public:Test(int* p = 0):_p(p){printf("Test()\n");}Test(const Test& r):_p(new int(*r._p)){printf("Test(&)\n");}Test(Test&& r):_p(r._p){r._p = NULL;printf("Test(&&)\n");}~Test(){printf("~Test() _p : %d\n", _p);delete _p;}Test& operator=(const Test& r){delete _p; _p = new int(*r._p); printf("operator=(&)\n");return *this;}Test& operator=(Test&& r){_p = r._p;r._p = NULL;printf("operator=(&&)\n");return *this;}int get()const{return *_p;}private:int* _p;};typedef unordered_map<int, Test> TestMap;int main(){TestMap t;t.insert(make_pair(1, Test(new int(10))));}
运行结果:

    结果表明,运行和预期效果相同,期间只调用了右值引用版的构造函数,那问题出在哪呢?,如果问题不是出在右值引用上那会是哪呢?

    于是我又仿造工程,将Boost.Pool加入其中:

#include <unordered_map>#include <cstdio>#include <boost/pool/pool_alloc.hpp>using namespace std;class Test{public:Test(int* p = 0):_p(p){printf("Test()\n");}Test(const Test& r):_p(new int(*r._p)){printf("Test(&)\n");}Test(Test&& r):_p(r._p){r._p = NULL;printf("Test(&&)\n");}~Test(){printf("~Test() _p : %d\n", _p);delete _p;}Test& operator=(const Test& r){delete _p; _p = new int(*r._p); printf("operator=(&)\n");return *this;}Test& operator=(Test&& r){_p = r._p;r._p = NULL;printf("operator=(&&)\n");return *this;}int get()const{return *_p;}private:int* _p;};typedef unordered_map<int, Test, hash<int>, equal_to<int>, boost::pool_allocator<pair<const int, Test> > > TestMap;int main(){TestMap t;t.insert(make_pair(1, Test(new int(10))));}
运行结果:

    看来是找到问题了,在工程中做出对应调整也证实了这一点,至于原因我想也没有必要去深究了,对应不完全遵守C++11标准的VS 2010和它是没有道理讲的。

    同时,出于好奇我又测试了一下Boost.unordered_map (Boost 1.55)

#include <boost/unordered_map.hpp>#include <cstdio>#include <boost/pool/pool_alloc.hpp>using namespace std;class Test{public:Test(int* p = 0):_p(p){printf("Test()\n");}Test(const Test& r):_p(new int(*r._p)){printf("Test(&)\n");}Test(Test&& r):_p(r._p){r._p = NULL;printf("Test(&&)\n");}~Test(){printf("~Test() _p : %d\n", _p);delete _p;}Test& operator=(const Test& r){delete _p; _p = new int(*r._p); printf("operator=(&)\n");return *this;}Test& operator=(Test&& r){_p = r._p;r._p = NULL;printf("operator=(&&)\n");return *this;}int get()const{return *_p;}private:int* _p;};typedef boost::unordered_map<int, Test, hash<int>, equal_to<int>, boost::pool_allocator<pair<const int, Test> > > TestMap;int main(){TestMap t;t.insert(make_pair(1, Test(new int(10))));}
运行结果:

    一切正常(除了比我预计的多调用了一次构造函数外),看来出问题的似乎还是VS自带的STL上面……

    事实证明,对应喜欢赶一步,拖N拍VS而言,大家还是不要把过多的C++11新特性往代码上加为好,还是老老实实的沿用C++03标准的习惯为好。同时也证明了Boost的确是一个很优秀的库

(找个机会在试试看其它容器是否也有这个问题)

----------------------------------------------------------------------------------------------------

2013.11.23 更新

    经过尝试,发现list、vector等容器均有问题,这让我对这个BUG有了兴趣:

    经过调试,发现了问题所在,看来我是错怪MS(P.J. Plauger)的STL了,问题还是出在Boost.Pool中,简单来说就是其fast_pool_allocator(pool_allocator)中没有定义右值引用版的construct函数,加上即可,默认在pool_alloc.hpp的400行(未修改pool_allocator)

void construct(const pointer ptr, value_type && t){ new (ptr) T(std::forward<value_type>(t)); }

    而Boost.unordered_map之所以能正常使用,是因为Boost充分考虑到了兼容的问题(兼容老式没有右值引用版的construct函数内存分配器),因此其只是用内存分配器构造了一个足够的空间,而使用自己的construct_value_impl函数对内容进行构造,所以即使内存分配器没有右值引用版的construct函数,其也可以正常使用

boost/unordered/detail/allocate.hpp (985)

template <typename Alloc, typename T, typename A0>inline void construct_value_impl(Alloc&, T* address,        emplace_args1<A0> const& args){    new((void*) address) T(boost::forward<A0>(args.a0));}