Boost Container vector 可以通过非原始指针管理内存吗?

Can Boost Container vector manage memory through non raw pointers?

我有一个类似指针的结构来代替指针。 与指针的区别在于它有额外的信息,(也是特殊的)分配器可以使用这些信息来释放内存。

这种类似指针的结构适用于所有基本用途。 我可以分配和取消分配内存、取消引用、递增、->

现在我想使用这个指针由类似STL的容器管理。 早些时候,我意识到 STL vector 基本上无法处理非原始指针。 T* 编码太硬,标准基本上排除了任何不是指针的东西。

受到 Boost.Interprocess' offset_ptr<T> 的启发,我决定使用 Boost.Container vector,这是非常可定制的,原则上可以管理任何东西,分配器传递给 boost::container::vector 可以处理任何类似指针的东西。

现在 class boost::container::vector<T, myallocator_with_special_pointer<T>> 可以做任何事情... 除了 resize()!!

查看 boost/container/vector.hpp 中的代码,似乎调整大小的过程(基本上是分配,然后是复制(或移动)和释放)涉及原始指针。

违规行是:

  [line 2729:] T * const new_buf = container_detail::to_raw_pointer
     (allocator_traits_type::allocate(this->m_holder.alloc(), new_cap, this->m_holder.m_start));

随后是

  [line 3022:] this->m_holder.start(new_start);  // new_start is the same as new_buf above. 
  // member ::start(pointer&) will need to convert a raw pointer to the pointer typedef.

这两行绝对消除了使用任何不是 raw_pointer 的可能性。即使我有一个原始指针的转换运算符,有关特殊指针的其他信息也会丢失。

这个小细节禁止使用非原始指针似乎很愚蠢。鉴于容器的所有努力都是通用的(例如定义 pointer typedef),为什么这部分代码使用 T* 只是为了调整大小?

换句话说,为什么 Boost Container 不使用这一行

  [alternative] pointer const new_buf = 
     allocator_traits_type::allocate(this->m_holder.alloc(), new_cap, this->m_holder.m_start);

是否有解决方法或替代方法来使用 Boost Container 向量来处理非原始指针?

Boost.Container 在其手册页中说 http://www.boost.org/doc/libs/1_64_0/doc/html/container/history_and_reasons.html#container.history_and_reasons.Why_boost_container

Boost.Container is a product of a long development effort that started in 2004 with the experimental Shmem library, which pioneered the use of standard containers in shared memory. Shmem included modified SGI STL container code tweaked to support non-raw allocator::pointer types and stateful allocators. Once reviewed, Shmem was accepted as Boost.Interprocess and this library continued to refine and improve those containers.

当前的实现(在调整大小的上下文中)违背了这个设计目标。


我在这里问了一个不太具体的问题,关于分配器的其他特征:


作为参考,指定特殊指针(传播到容器)的分配器是这样的,

template<class T>
struct allocator{
    using value_type = T;
    using pointer = array_ptr<T>; // simulates T*
    using const_pointer = array_ptr<T const>; // simulates T const*
    using void_pointer = array_ptr<void>; // simulates void*
    using const_void_pointer = array_ptr<void const>; // simulates void const*
    some_managed_shared_memory& msm_;
    allocator(some_managed_shared_memory& msm) : msm_(msm){}
    array_ptr<T> allocate(mpi3::size_t n){
        auto ret = msm_.allocate(n*sizeof(T));
        return static_cast<array_ptr<T>>(ret);
    }
    void deallocate(array_ptr<T> ptr, mpi3::size_t = 0){
        msm_.deallocate(ptr);
    }
};

完整的工作代码http://coliru.stacked-crooked.com/a/f43b6096f9464cbf

#include<iostream>
#include <boost/container/vector.hpp>

template<typename T>
struct array_ptr;

template<>
struct array_ptr<void> {
    using T = void;
    T* p;
    int i; //some additional information

//    T& operator*() const { return *p; }
    T* operator->() const { return p; }

//    operator T*() const { return p; }
    template<class TT>
    operator array_ptr<TT>() const{return array_ptr<TT>((TT*)p, i);}
    operator bool() const{return p;}
    array_ptr(){}
    array_ptr(std::nullptr_t) : p(nullptr){}
    array_ptr(T* ptr, int _i) : p(ptr), i(_i){}
    template<class Other>
    array_ptr(array_ptr<Other> other) : p(other.p), i(other.i){}
};

template<>
struct array_ptr<void const> {
    using T = void const;
    T* p;
    int i; //some additional information

//    T& operator*() const { return *p; }
    T* operator->() const { return p; }

    operator T*() const { return p; }
    array_ptr(){}
    array_ptr(std::nullptr_t) : p(nullptr){}
    array_ptr(T* ptr, int _i) : p(ptr), i(_i){}
    template<class Other>
    array_ptr(array_ptr<Other> other) : p(other.p), i(other.i){}
};

template<typename T>
struct array_ptr {
    T* p;
    int i; //some additional information

    T& operator*() const { return *p; }
    T* operator->() const { return p; }
    T& operator[](std::size_t n) const{
        assert(i == 99);
        return *(p + n);
    }
    bool operator==(array_ptr const& other) const{return p == other.p and i == other.i;}
    bool operator!=(array_ptr const& other) const{return not((*this)==other);}

//    operator T*() const { return p; }
    array_ptr& operator++(){++p; return *this;}
    array_ptr& operator+=(std::ptrdiff_t n){p+=n; return *this;}
    array_ptr& operator-=(std::ptrdiff_t n){p-=n; return *this;}
    array_ptr operator+(std::size_t n) const{array_ptr ret(*this); ret+=n; return ret;}
    std::ptrdiff_t operator-(array_ptr const& other) const{return p - other.p;}
    array_ptr(){}
    array_ptr(std::nullptr_t) : p(nullptr), i(0){}

    operator bool() const{return p;}

    array_ptr(T* ptr, int _i) : p(ptr), i(_i){}
    array_ptr(T* ptr) : p(ptr), i(0){}
    array_ptr(int) : p(nullptr), i(0){}
    array_ptr(array_ptr<void> const& other) : p(static_cast<T*>(other.p)), i(other.i){}
};

struct some_managed_shared_memory {
    array_ptr<void> allocate(size_t n) { return array_ptr<void>(::malloc(n), 99); }
    void  deallocate(array_ptr<void> ptr) { if (ptr) ::free(ptr.p); }
};

template<typename T>
struct allocator{
    using value_type = T;
    using pointer = array_ptr<T>; // simulates T*
    using const_pointer = array_ptr<T const>; // simulates T const*
    using void_pointer = array_ptr<void>; // simulates void*
    using const_void_pointer = array_ptr<void const>; // simulates void const*

    some_managed_shared_memory& msm_;
    allocator(some_managed_shared_memory& msm) : msm_(msm){}
    array_ptr<T> allocate(size_t n){
        auto ret = msm_.allocate(n*sizeof(T));
        return static_cast<array_ptr<T>>(ret);
    }
    void deallocate(array_ptr<T> ptr, std::size_t = 0){
        msm_.deallocate(ptr);
    }
};

int main() {
    some_managed_shared_memory realm;
    boost::container::vector<int, allocator<int> > v(10, realm);
    assert( v[4] == 0 );
    v[4] = 1;
    assert( v[4] == 1 );
    for(std::size_t i = 0; i != v.size(); ++i) std::cout << v[i] << std::endl;
    for(auto it = v.begin(); it != v.end(); ++it) std::cout << *it << std::endl;

    // none of these compile:
    v.push_back(8);
    assert(v.size() == 11);
    v.resize(100);
    std::cout << v[89] << std::endl; // will fail an assert because the allocator information is lost
    //v.assign({1,2,3,4,5});
}

我调查了事情。

TL;DR 似乎是:支持非原始指针,但在某些操作中它们需要从原始指针进行隐式转换。这是否是设计使然,我不知道,但它似乎与设计目标并不矛盾。

事实上,这与分配器支持的历史非常相似:STL 容器支持自定义分配器,但不支持 stateful 分配器(意思是,非默认可构造的分配器类型)。

分配器版本

起初我尝试了一些分配器版本:

using version = boost::container::version_0; // seems unsupported, really
using version = boost::container::version_1;
using version = boost::container::version_2; // does different operations

但是没有(决定性的)作用。也许文档有线索。

指针运算

之后我查看了具体的错误。看着引用的line/error,我突然意识到原始指针可能是个意外。查看这些输出:

std::cout << boost::container::container_detail::impl::version<allocator<int> >::value << "\n";

array_ptr<int> p;
auto rawp = boost::container::container_detail::to_raw_pointer(p);
std::cout << typeid(rawp).name() << "\n";

std::cout << typeid(p).name() << "\n";
std::cout << typeid(p + 5).name() << "\n";
std::cout << typeid(p - 5).name() << "\n";

显示类似于¹

1
int*
array_ptr<int>
int*
int*

¹ 在 c++filt -t

的帮助下进行了美化

这让我定义了指针算法:

template <typename T, typename N>
array_ptr<T> operator+(array_ptr<T> const& p, N n) { return array_ptr<T>(p.p+n, p.i); }

template <typename T>
array_ptr<T>& operator++(array_ptr<T>& p) { return ++p.p, p; }

template <typename T>
array_ptr<T> operator++(array_ptr<T>& p, int) { auto q = p.p++; return array_ptr<T>(q, p.i); }

template <typename T, typename N>
array_ptr<T> operator-(array_ptr<T> const& p, N n) { return array_ptr<T>(p.p-n, p.i); }

template <typename T>
ptrdiff_t operator-(array_ptr<T> const& a, array_ptr<T> const& b) { return a.p - b.p; }

现在输出变成

1
int*
array_ptr<int>
array_ptr<int>
array_ptr<int>

更多用例使用这些定义成功编译。假设 array_pointer 里面的 "annotation" 数据在递增后是有效的,它不应该丢失任何分配器信息

真正的罪魁祸首

除此之外,有些东西仍然无法编译。具体来说,在某些地方,分配器的 pointer 类型是从原始指针构造回来的。这失败了,因为没有合适的 "default" 转换构造函数。如果你用可选数据值声明构造函数,一切都会编译,但你可能会争辩说这会丢失信息,因为有一条来自

的路径
 array_pointer<T> p;
 auto* rawp = to_raw_pointer(p);
 array_pointer<T> clone(rawp); // oops lost the extra info in p

观察

请注意,正如您显然意识到的那样(从注释运算符判断),添加默认构造函数参数消除了对算术运算的需要(预递增除外)。

但是,添加它们可确保有损转换路径的使用频率降低,这对您的用例可能很重要。

演示时间

Live On Coliru

#if COMPILATION_INSTRUCTIONS
clang++ -std=c++14 -Wall -Wfatal-errors [=16=] -o [=16=]x.x && [=16=]x.x $@ && rm -f [=16=]x.x; exit
#endif

#define DEFAULT_DATA = 0
#define DEFINE_ARITHMETIC_OPERATIONS

#include <iostream>
#include <boost/container/vector.hpp>
#include <typeinfo>

template<typename T>
struct array_ptr {
    T* p;
    int i; //some additional information

    T& operator*() const { return *p; }
    T* operator->() const { return p; }

    operator T*() const { return p; }

    array_ptr(){}
    //array_ptr(std::nullptr_t) : p(nullptr), i(0){}
    array_ptr(T* ptr, int _i DEFAULT_DATA) : p(ptr), i(_i){}

};

template<>
struct array_ptr<void> {
    using T = void;
    T* p;
    int i; //some additional information

//    T& operator*() const { return *p; }
    T* operator->() const { return p; }

    operator T*() const { return p; }
    template<class T>
    operator array_ptr<T>() const{return array_ptr<T>((T*)p, i);}
//    array_ptr& operator++(){++p; return *this;}
    array_ptr(){}
    array_ptr(std::nullptr_t) : p(nullptr){}
    array_ptr(T* ptr, int _i DEFAULT_DATA) : p(ptr), i(_i){}
    template<class Other>
    array_ptr(array_ptr<Other> other) : p(other.p), i(other.i){}
};

template<>
struct array_ptr<void const> {
    using T = void const;
    T* p;
    int i; //some additional information

//    T& operator*() const { return *p; }
    T* operator->() const { return p; }

    operator T*() const { return p; }
//    array_ptr& operator++(){++p; return *this;}
//  template<class Other> array_ptr(array_ptr<Other> const& other) : p(other.p), i(other.i){}
    array_ptr(){}
    array_ptr(std::nullptr_t) : p(nullptr){}
    array_ptr(T* ptr, int _i DEFAULT_DATA) : p(ptr), i(_i){}
    template<class Other>
    array_ptr(array_ptr<Other> other) : p(other.p), i(other.i){}
};

struct some_managed_shared_memory {
    array_ptr<void> allocate(size_t n) { return array_ptr<void>(::malloc(n), 99); }
    void  deallocate(array_ptr<void> ptr) { if (ptr) ::free(ptr.p); }
};

template<typename T>
struct allocator{
    using version = boost::container::version_1;

    using value_type = T;
    using pointer = array_ptr<T>; // simulates T*
    using const_pointer = array_ptr<T const>; // simulates T const*
    using void_pointer = array_ptr<void>; // simulates void*
    using const_void_pointer = array_ptr<void const>; // simulates void const*

    some_managed_shared_memory& msm_;
    allocator(some_managed_shared_memory& msm) : msm_(msm){}
    array_ptr<T> allocate(size_t n){
        auto ret = msm_.allocate(n*sizeof(T));
        return static_cast<array_ptr<T>>(ret);
    }
    void deallocate(array_ptr<T> ptr, std::size_t = 0){
        msm_.deallocate(ptr);
    }
};

#ifdef DEFINE_ARITHMETIC_OPERATIONS
    template <typename T, typename N>
    array_ptr<T> operator+(array_ptr<T> const& p, N n) { return array_ptr<T>(p.p+n, p.i); }

    template <typename T>
    array_ptr<T>& operator++(array_ptr<T>& p) { return ++p.p, p; }

    template <typename T>
    array_ptr<T> operator++(array_ptr<T>& p, int) { auto q = p.p++; return array_ptr<T>(q, p.i); }

    template <typename T, typename N>
    array_ptr<T> operator-(array_ptr<T> const& p, N n) { return array_ptr<T>(p.p-n, p.i); }

    template <typename T>
    ptrdiff_t operator-(array_ptr<T> const& a, array_ptr<T> const& b) { return a.p - b.p; }
#endif


int main() {
    std::cout << boost::container::container_detail::impl::version<allocator<int> >::value << "\n";

    if (1) { // some diagnostics
        array_ptr<int> p;
        auto rawp = boost::container::container_detail::to_raw_pointer(p);
        std::cout << typeid(rawp).name() << "\n";

        std::cout << typeid(p).name() << "\n";
        std::cout << typeid(p + 5).name() << "\n";
        std::cout << typeid(p - 5).name() << "\n";
    }

    some_managed_shared_memory realm;
    boost::container::vector<int, allocator<int> > v(10, realm);
    assert( v[4] == 0 );
    v[4] = 1;
    assert( v[4] == 1 );
    for(std::size_t i = 0; i != v.size(); ++i) std::cout << v[i] << std::endl;

    // these compile:
    v.push_back(12);
    v.resize(100);
    v.assign({1,2,3,4,5});
}

版画

1
Pi
9array_ptrIiE
9array_ptrIiE
9array_ptrIiE
0
0
0
0
1
0
0
0
0
0