为什么不能在std :: vector< T>中包装一个T *? [英] Why can't I wrap a T* in an std::vector<T>?

查看:349
本文介绍了为什么不能在std :: vector< T>中包装一个T *?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 T * 寻址一个缓冲区 len 类型 / code>。由于某些原因,我需要以 std :: vector< T> 的形式提供此数据。据我所知,我不能构造一个使用我的缓冲区作为其内部存储的向量。为什么?



注意:




  • 请不要建议我使用

  • 我不介意向量必须在以后调整大小后复制数据。

  • 我问的是的原因,而不是的可能性

  • 这个问题特别困扰我现在C ++已移动语义。


解决方案

如果我们可以从其脚下拉出对象的存储, <>您可以。



您写的是 std :: vector< T> c $ c> std :: vector 采用两个模板参数,而不只是一个。第二个模板参数指定要使用的分配器类型,向量的构造函数具有允许传递该分配器类型的自定义实例的重载。



所以你需要做的是编写一个分配器,在可能的情况下使用你自己的内部缓冲区,并且当你自己的内部缓冲区已满时,会返回到默认分配器。



默认分配器不可能希望处理它,因为它不会有任何线索可以释放内存的位,哪些不能。






一个带有内部缓冲区的样例状态分配器,其中包含已经构造的元素,不应被向量覆盖,包括一个大陷阱的演示:

  struct my_allocator_state {
void * buf;
std :: size_t len;
bool bufused;
const std :: type_info * type;
};

template< typename T>
struct my_allocator {
typedef T value_type;

my_allocator(T * buf,std :: size_t len)
:def(),state(std :: make_shared< my_allocator_state,my_allocator_state>({buf,len,false,& typeid(T)})){}

template< std :: size_t N>
my_allocator(T(& buf)[N])
:def(),state(std :: make_shared< my_allocator_state,my_allocator_state>({buf,N,false,& typeid(T) })){}

template< typename U>
friend struct my_allocator;

template< typename U>
my_allocator(my_allocator< U> other)
:def(),state(other.state){}

T * allocate(std :: size_t n)
{
if(!state-> bufused& n == state-> len&&&typeid(T)== * state-> type)
{
state-> bufused = true;
return static_cast< T *>(state-> buf);
}
else
return def.allocate(n);
}

void deallocate(T * p,std :: size_t n)
{
if(p == state-> buf)
state-> bufused = false;
else
def.deallocate(p,n);
}

template< typename ... Args>
void construct(T * c,Args ... args)
{
if(!in_buffer(c))
def.construct(c,std :: forward& ;(args)...);
}

void destroy(T * c)
{
if(!in_buffer(c))
def.destroy
}

friend bool operator ==(const my_allocator& a,const my_allocator& b){
return a.state == b.state;
}

friend bool operator!=(const my_allocator& a,const my_allocator& b){
return a.state!= b.state;
}

private:
std :: allocator< T> def;
std :: shared_ptr< my_allocator_state>州;

bool in_buffer(T * p){
return * state-> type == typeid(T)
&& points_into_buffer(p,static_cast< T *>(state-> buf),state-> len);
}
};

int main()
{
int buf [] = {1,2,3,4};
std :: vector< int,my_allocator< int>> v(sizeof buf / sizeof * buf,{},buf);
v.resize(3);
v.push_back(5);
v.push_back(6);
for(auto& i:v)std :: cout<< i<< std :: endl;
}

输出:

 
1
2
3
4
6

push_back of 5 适合旧缓冲区,因此构建被绕过。当添加 6 时,将分配新的内存,并且一切都开始正常。您可以通过向您的分配器添加一个方法来避免这个问题,表明从那时起,构建不应再绕过。



points_into_buffer 原来是最难写的部分,我从我的答案中省略了。目标语义应该从我如何使用它显而易见。请参阅我的问题这里是一个可移植的实现在我的答案,或如果你的实现允许,请使用其他问题中的一个较简单的版本。



顺便说一下,我不是真的很高兴一些实现如何使用 rebind 以这样的方式,没有避免存储运行时类型信息以及状态,但如果你的实现不'需要,你可以通过使状态一个模板类(或嵌套类),使它更简单一些。


I have a T* addressing a buffer with len elements of type T. I need this data in the form of an std::vector<T>, for certain reasons. As far as I can tell, I cannot construct a vector which uses my buffer as its internal storage. Why is that?

Notes:

  • Please don't suggest I use iterators - I know that's usually the way around such issues.
  • I don't mind that the vector having to copy data around if it's resized later.
  • I'm asking about the reason, not about the possibility.
  • This question especially baffles me now that C++ has move semantics. If we can pull an object's storage from under its feet, why not be able to shove in our own?

解决方案

You can.

You write about std::vector<T>, but std::vector takes two template arguments, not just one. The second template argument specifies the allocator type to use, and vector's constructors have overloads that allow passing in a custom instance of that allocator type.

So all you need to do is write an allocator that uses your own internal buffer where possible, and falls back to asking the default allocator when your own internal buffer is full.

The default allocator cannot possibly hope to handle it, since it would have no clue on which bits of memory can be freed and which cannot.


A sample stateful allocator with an internal buffer containing already-constructed elements that should not be overwritten by the vector, including a demonstration of a big gotcha:

struct my_allocator_state {
    void *buf;
    std::size_t len;
    bool bufused;
    const std::type_info *type;
};

template <typename T>
struct my_allocator {
    typedef T value_type;

    my_allocator(T *buf, std::size_t len)
        : def(), state(std::make_shared<my_allocator_state, my_allocator_state>({ buf, len, false, &typeid(T) })) { }

    template <std::size_t N>
    my_allocator(T(&buf)[N])
        : def(), state(std::make_shared<my_allocator_state, my_allocator_state>({ buf, N, false, &typeid(T) })) { }

    template <typename U>
    friend struct my_allocator;

    template <typename U>
    my_allocator(my_allocator<U> other)
        : def(), state(other.state) { }

    T *allocate(std::size_t n)
    {
        if (!state->bufused && n == state->len && typeid(T) == *state->type)
        {
            state->bufused = true;
            return static_cast<T *>(state->buf);
        }
        else
            return def.allocate(n);
    }

    void deallocate(T *p, std::size_t n)
    {
        if (p == state->buf)
            state->bufused = false;
        else
            def.deallocate(p, n);
    }

    template <typename...Args>
    void construct(T *c, Args... args)
    {
        if (!in_buffer(c))
            def.construct(c, std::forward<Args>(args)...);
    }

    void destroy(T *c)
    {
        if (!in_buffer(c))
            def.destroy(c);
    }

    friend bool operator==(const my_allocator &a, const my_allocator &b) {
        return a.state == b.state;
    }

    friend bool operator!=(const my_allocator &a, const my_allocator &b) {
        return a.state != b.state;
    }

private:
    std::allocator<T> def;
    std::shared_ptr<my_allocator_state> state;

    bool in_buffer(T *p) {
        return *state->type == typeid(T)
            && points_into_buffer(p, static_cast<T *>(state->buf), state->len);
    }
};

int main()
{
    int buf [] = { 1, 2, 3, 4 };
    std::vector<int, my_allocator<int>> v(sizeof buf / sizeof *buf, {}, buf);
    v.resize(3);
    v.push_back(5);
    v.push_back(6);
    for (auto &i : v) std::cout << i << std::endl;
}

Output:

1
2
3
4
6

The push_back of 5 fits into the old buffer, so construction is bypassed. When 6 is added, new memory is allocated, and everything starts acting as normal. You could avoid that problem by adding a method to your allocator to indicate that from that point onward, construction should not be bypassed any longer.

points_into_buffer turned out to be the hardest part to write, and I've omitted that from my answer. The intended semantics should be obvious from how I'm using it. Please see my question here for a portable implementation in my answer there, or if your implementation allows it, use one of the simpler versions in that other question.

By the way, I'm not really happy with how some implementations use rebind in such ways that there is no avoiding storing run-time type info along with the state, but if your implementation doesn't need that, you could make it a bit simpler by making the state a template class (or a nested class) too.

这篇关于为什么不能在std :: vector&lt; T&gt;中包装一个T *?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆