将动态数组包装成STL/Boost容器?

8
我需要将动态分配的数组(例如a = new double[100])包装成std::vector(最好是)而不复制数组。 这个限制是因为我想要包装的数组是从文件映射的,所以只做vector(a, a+size)会使内存使用量翻倍。 有什么技巧可以做到这一点吗?

1
无论如何,您都无法获得std::vector的全部功能集(例如resize可能无法工作),因此列出需要包装器具体提供什么可能是有益的。 - maxim1000
8个回答

11

其中一个最好的解决方案是类似于STLSoft的array_proxy<>模板。不幸的是,由doxygen从源代码生成的文档页面并没有太多帮助理解该模板。实际上,源代码可能会更好:

array_proxy<> 模板在Matthew Wilson的书《Imperfect C++》中描述得很好。我使用的版本是STLSoft网站上的精简版,所以我不需要引入整个库。我的版本不太可移植,但比STLSoft上的版本简单得多(后者跳过了许多可移植性障碍)。

如果您这样设置变量:

int myArray[100];

array_proxy<int> myArrayProx( myArray);
变量myArrayProx有许多STL接口-begin()end()size(),迭代器等等。

因此,在许多方面,array_proxy<>对象的行为就像向量一样(虽然push_back()不在那里,因为array_proxy<>无法增长-它不管理数组的内存,只是用更接近向量的东西包裹它)。

array_proxy<>的一个非常好的特点是,如果将它们用作函数参数类型,则函数可以确定传入的数组的大小,这对于原生数组来说是不正确的。并且包装的数组的大小不是模板类型的一部分,因此使用起来非常灵活。


1
+1,似乎是最好的解决方案,特别是因为代理不应该重新分配内存,所以它应该避免许多问题。 - Roman Nikitchenko

9

boost::iterator_range提供类似于容器的接口:

// Memory map an array of doubles:
size_t number_of_doubles_to_map = 100;
double* from_mmap = mmap_n_doubles(number_of_doubles_to_map);

// Wrap that in an iterator_range
typedef boost::iterator_range<double*> MappedDoubles;
MappedDoubles mapped(from_mmap, from_mmap + number_of_doubles_to_map);

// Use the range
MappedDoubles::iterator b = mapped.begin();
MappedDoubles::iterator e = mapped.end();
mapped[0] = 1.1;
double first = mapped(0);

if (mapped.empty()){
    std::cout << "empty";
}
else{
    std::cout << "We have " << mapped.size() << "elements. Here they are:\n"
       << mapped;
}

1
对我来说最好的答案,因为它使用了boost! - CharlesB

2
我曾下定决心要实现同样的事情。经过几天的思考和尝试,我决定这不值得。最终,我创建了自己的自定义向量,它的行为类似于std::vector,但只具有我实际需要的功能,如边界检查、迭代器等。
如果你仍然想使用std::vector,那么我当时能想到的唯一方法是创建一个自定义分配器。我从未编写过这样的分配器,但既然这是控制STL内存管理的唯一方式,也许在那里可以做些什么。

2
我建议不要编写自己的容器。大多数情况下,这样做并不值得,而且会导致相当多的互操作性问题。此外,花费在调试重新发明的东西上的时间是浪费时间。然而,自定义分配器可能相当容易实现,并且是更好的方法。 - MP24
通常而言我会同意,但我真的觉得这取决于你要用它来做什么。std::vector中的大部分“复杂”操作可能与内存管理有关,在这种情况下,它们都不是必需的。因此编写一个具有边界检查和指针作为迭代器的小型类应该不会花费太长时间。由于我不知道作者打算在哪里/如何使用它,所以无法评论互操作性。但迭代器支持应该使其能够与STL的算法一起使用。 - Idan K

1

不,使用std::vector是不可能的。

但是如果可能的话,您可以创建具有此大小的向量,并将文件映射到它。

std::vector<double> v(100);
mmapfile_double(&v[0], 100);

1

关于指向映射区域元素的指针向量(内存消耗较小,因为sizeof(double*) < sizeof(double)),你怎么看?这对你来说可以吗?

有一些缺点(主要是需要特殊的排序谓词),但也有一些好处,例如,您可以删除元素而不更改实际映射内容(或者具有不同元素顺序的偶数个这样的数组,而不对实际值进行任何更改)。

所有使用std::vector在映射文件上的解决方案都存在一个常见问题:将向量内容“固定”到映射区域。这无法跟踪,您只能注意自己不要使用可能导致向量内容重新分配的东西。因此,在任何情况下都要小心。


1
你可以使用 array_proxy<>,或查看 Boost.Array。它会给你提供 size()、front()、back()、at()、operator[] 等等函数。个人而言,我更喜欢 Boost.Array,因为 Boost 更常用。

1

嗯,向量模板允许提供自己的内存分配器。我自己从未尝试过,但我猜想将其指向您的数组可能并不那么困难,也许可以使用放置 new 运算符...只是猜测,如果我尝试并成功了,我会写更多的。


0

这是你问题的解决方案。在我想出可行的解决方案之前,我一直在尝试这个问题。注意事项是,在使用后必须将指针清零,以避免重复释放内存。

#include <vector>
#include <iostream>

template <class T>
void wrapArrayInVector( T *sourceArray, size_t arraySize, std::vector<T, std::allocator<T> > &targetVector ) {
  typename std::_Vector_base<T, std::allocator<T> >::_Vector_impl *vectorPtr =
    (typename std::_Vector_base<T, std::allocator<T> >::_Vector_impl *)((void *) &targetVector);
  vectorPtr->_M_start = sourceArray;
  vectorPtr->_M_finish = vectorPtr->_M_end_of_storage = vectorPtr->_M_start + arraySize;
}

template <class T>
void releaseVectorWrapper( std::vector<T, std::allocator<T> > &targetVector ) {
  typename std::_Vector_base<T, std::allocator<T> >::_Vector_impl *vectorPtr =
        (typename std::_Vector_base<T, std::allocator<T> >::_Vector_impl *)((void *) &targetVector);
  vectorPtr->_M_start = vectorPtr->_M_finish = vectorPtr->_M_end_of_storage = NULL;
}

int main() {

  int tests[6] = { 1, 2, 3, 6, 5, 4 };
  std::vector<int> targetVector;
  wrapArrayInVector( tests, 6, targetVector);

  std::cout << std::hex << &tests[0] << ": " << std::dec
            << tests[1] << " " << tests[3] << " " << tests[5] << std::endl;

  std::cout << std::hex << &targetVector[0] << ": " << std::dec
            << targetVector[1] << " " << targetVector[3] << " " << targetVector[5] << std::endl;

  releaseVectorWrapper( targetVector );
}

或者你可以创建一个继承自vector的类,在销毁时将指针置空:

template <class T>
class vectorWrapper : public std::vector<T>
{   
public:
  vectorWrapper() {
    this->_M_impl _M_start = this->_M_impl _M_finish = this->_M_impl _M_end_of_storage = NULL;
  }   

  vectorWrapper(T* sourceArray, int arraySize)
  {   
    this->_M_impl _M_start = sourceArray;
    this->_M_impl _M_finish = this->_M_impl _M_end_of_storage = sourceArray + arraySize;
  }   

  ~vectorWrapper() {
    this->_M_impl _M_start = this->_M_impl _M_finish = this->_M_impl _M_end_of_storage = NULL;
  }   

  void wrapArray(T* sourceArray, int arraySize)
  {   
    this->_M_impl _M_start = sourceArray;
    this->_M_impl _M_finish = this->_M_impl _M_end_of_storage = sourceArray + arraySize;
  }   
};  

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接