2015-01-01 41 views
2

我有一个T*寻址lenT类型的元素的缓冲区。由于某些原因,我需要以std::vector<T>的形式提供这些数据。据我所知,我不能构建一个使用我的缓冲区作为内部存储的向量。这是为什么?为什么我不能在一个std :: vector <T>中包装一个T *?

注:

  • 请不要建议我使用迭代 - 我知道这通常是围绕此类问题的方式。
  • 我不介意如果以后调整大小,矢量不得不复制数据。
  • 这个问题特别让我感到困惑,因为C++已经移动了语义。如果我们可以从它的脚下拉出一个物体的存储器,为什么不能自己推动呢?
+0

为什么不直接将元素顺序移动到矢量中?想必你把它们放到一个矢量中,以便以后可以调整集合的大小,所以你预计它们会被移动,对吗?如果无论如何会发生这种情况,可能会多次,那么避免它发生一次可能是一个不成熟的优化。 –

+0

@克里斯贝克:不,这不是我把它们放在向量中的原因。假设我有一些STLish代码需要一个向量,而不是一个原始数组。 – einpoklum

+0

我想你不能使用'std :: array'或者是因为动态调整大小或者是可能的?也许在你的原始缓冲区中有某种类型的包装类型,它具有类似于STL的接口,足以满足你的需要。我的猜测是,从头开始做这样的事情可能更容易,然后搞乱分配器 –

回答

11

您可以。

你写了std::vector<T>,但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>(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; 
} 

输出:

 
1 
2 
3 
4 
6 

5push_back适合旧的缓冲区,所以建筑被绕过。当添加6时,将分配新内存,并且所有内容都按照正常方式开始工作。你可以通过向你的分配器添加一个方法来避免这个问题,以表明从那时起,建设不应该被绕过。

points_into_buffer原来是最难写的部分,我从我的回答中忽略了这一点。预期的语义应该从我如何使用它中显而易见。请参阅my question here以获得便携式实现,或者如果您的实现允许,请使用其他问题中较简单的版本之一。顺便说一下,我不是很满意一些实现如何使用rebind这样的方式,以避免存储运行时类型信息以及状态,但是如果你的实现不需要这样做,那么你可以通过使状态成为模板类(或嵌套类)来使其更简单一些。

+0

这样的分配器可以作为标准库的一部分吗? – einpoklum

+0

@einpoklum Boost确实有一些自定义分配器,但我不知道任何具有您要查找的特定行为。 – hvd

+1

这项工作?我们想要混合赋值/初始化和重用缓冲区(而不是无意义的自身拷贝)。 – Yakk

6

简短的回答是,矢量不能使用你的缓冲区,因为它不是这样设计的。

它也是有意义的。如果一个向量没有分配它自己的内存,当添加更多的项目时它如何调整缓冲区的大小?它分配一个新的缓冲区,但它对旧的有什么影响?同样适用于移动 - 如果矢量不控制自己的缓冲区,它如何控制这个缓冲区到另一个实例?

+0

zmbq:不,没有意义。首先,假设它是一个const向量;它永远不会调整。其次,即使它是可变的,调整大小也会使缓冲区重新分配成为“公平游戏”,并且你没有得到任何承诺。可以提出一个类似的说法。 – einpoklum

+1

那么,如果它不知道如何得到它,那么如何重新分配你的原始'T *'?答案 - 它不能。并且破解分配器来跟踪这个仍然没有给你任何方式通知载体缓冲区已经被填充。 – Useless

相关问题