5

我有一个类似指针的结构来代替指针。与指针的不同之处在于它具有(也是特殊的)分配器可以用来释放内存的额外信息。

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

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

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

现在班级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. 即使我有一个原始指针的转换运算符,有关特殊指针的其他信息也会丢失。

这个小细节禁止使用非原始指针似乎很愚蠢。考虑到容器的通用性(例如定义pointertypedef),为什么这部分代码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 是 2004 年从实验性 Shmem 库开始的长期开发工作的产物,该库率先在共享内存中使用标准容器。Shmem 包含经过修改的 SGI STL 容器代码,经过调整以支持非原始 allocator::pointer类型和有状态分配器。经过审查,Shmem 被接受为 Boost.Interprocess,并且该库继续完善和改进这些容器。

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


我在这里问了一个不太具体的问题,关于分配器的其他特征:是否仍然可以自定义 STL 向量的“引用”类型?


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

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});
}
4

1 回答 1

2

我调查了一些事情。

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

事实上,这与分配器支持的历史非常相似:STL 容器支持自定义分配器,但不支持有状态分配器(即非默认构造的分配器类型)。

分配器版本

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

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

但它没有(决定性的)效果。也许文档有线索。

指针算术

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

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递增后是有效的,应该不会丢失任何分配器信息

真正的罪魁祸首

有了这个,有些东西仍然无法编译。具体来说,在某些情况下,分配器的pointer类型是从原始指针构造回来的。这失败了,因为没有合适的“默认”转换构造函数。如果你用可选的数据值声明构造函数,一切都会编译,但你可能会争辩说这会丢失信息,因为有一条路径来自

 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 $0 -o $0x.x && $0x.x $@ && rm -f $0x.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
于 2017-07-07T16:30:03.747 回答