【问题标题】:are there center-allocation deque or vector in STL implementations?STL 实现中是否有中心分配双端队列或向量?
【发布时间】:2014-08-29 15:30:30
【问题描述】:

我正在阅读有关 deques 与 vectors 的文章,并遇到了它的 wikipedia entry,它说使用动态数组的 deque 的三种可能实现之一是:

从底层数组的中心分配双端队列内容,以及 到达任一端时调整底层数组的大小。这 方法可能需要更频繁地调整大小并浪费更多空间, 特别是当元素只插入一端时。

我想知道是否有任何 STL(或 STL 风格)实现实际使用这种中心分配策略?

我之所以这么问是因为这个策略看起来相当吸引人,因为它只涉及一个底层数组,因此消除了内存不连续问题,这可能是dequevector 相比的唯一主要问题。如果我理解正确,这很可能是 std::vector 的替代品,它允许 O(1) pop_front(摊销)或 deque 的替代品,具有内存连续性保证。我假设这是以将std::vector 的缓冲空间增加一倍为代价的,这对我的用例来说不是一个主要问题。

另外,在这样的容器中间插入/删除是否会平均花费 std::vector 一半的时间?


更新:

正如@Lightness Races in Orbit 指出的那样,在当前标准下不会使用这样的实现,因为没有人可以从与 STL 的每份合同中受益,而每个人都会受到不利影响。关于缺点,我还有一个问题是:

是否可以实现一个新的 vectordeque 类似容器(比如bivector),这样除了std::vector 的功能/操作符之外,

1) 提供(摊销的)恒定时间push_front()pop_front() 操作和

2) 在增加大小后保证内存连续性但不保证迭代器有效性?

我想在幕后使用一个数组,deque 上的许多间接/性能问题都会消失。

【问题讨论】:

  • 在到达数组末端后,您是否还需要移动内容以保持连续性?
  • 也许我们可以像std::vector 那样在到达右端时重新分配内存。我认为重新分配的成本在使用非破坏插入平均时与vector 一样摊销为 O(1)。我想我们可以“在两端”使用vector 技巧。

标签: c++ c++11 vector stl deque


【解决方案1】:

你的问题是你缺少那个容器。从这样的开始:

template<typename T>
class bi_vec {
  std::unique_ptr<char[]> raw;
  std::size_t first = 0;
  std::size_t last = 0;
  std::size_t capacity = 0;
  char* raw_get( std::size_t index ) {
    return &raw[index*sizeof(T)];
  }
  char const* raw_get( std::size_t index ) const {
    return &raw[index*sizeof(T)];
  }
  T& get( std::size_t index ) {
    return *reinterpret_cast<T*>(raw_get(index));
  }
  T const& get( std::size_t index ) const {
    return *reinterpret_cast<T const *>(raw_get(index));
  }
  char* raw_before() {
    if (first < 1)
      grow();
    --first;
    return raw_get(first);
  }
  char* raw_after() {
    if (last+1 >= capacity)
      grow();
    ++last;
    return raw_get(last-1);
  }
  void grow() {
    std::vector new_capacity = (capacity+1)*2;
    std::size_t new_first = (new_capacity - (last-first)) / 2;
    std::size_t new_last = new_capacity - new_first;
    std::unique_ptr<char[]> new_buff( new char[new_capacity*sizeof(T)] );
    T* b = &get(0);
    T* e = &get(last-first);
    std::move( b, e, reinterpret_cast<T*>( &new_buff[new_first*sizeof(T)] ) );
    std::swap( buff, raw );
    std::swap( new_capacity, capacity );
    std::swap( new_first, first );
    std::swap( new_last, last );
    for (T* it = b; it != e; ++it) {
      it->~T();
    }
  }
public:
  T& operator[]( std::size_t index ) { return get(index); }
  T const& operator[]( std::size_t index ) const { return get(index); }
  template<class... Us>
  void emplace_back( Us&&...us ) {
    char* a = raw_after();
    new (a) T( std::forward<Us>(us) );
  }
  template<class... Us>
  void emplace_front( Us&&...us ) {
    char* a = raw_before();
    new (a) T( std::forward<Us>(us) );
  }
  ~bi_vec() {
    for( std::size_t i = 0; i != last-first; ++i ) {
      get(i).~T();
    }
  }
};

并添加迭代器(我很想使用boost 迭代器助手,或原始指针来启动)和您需要的任何接口。请注意,上述内容需要工作以确保其保持异常安全。

【讨论】:

  • 谢谢。我试试看。
  • 这是我目前所拥有的:coliru.stacked-crooked.com/a/43d045883dc4666e 可以完全实例化为bi_vec&lt;int&gt;,尽管没有进行其他编译,更不用说测试了。实际上我保持它相当简单,所以任何错误都应该很少(更不用说严重性)
  • @Mooing Duck,非常感谢!你花了多长时间编程所有这些。您的代码在我的计算机上编译正常,在 TDM-MinGW64 上使用 gcc-4.8.1。但是,当我实例化像 bi_vec&lt;int&gt; a; 这样的变量时,会出现一些错误
  • 大约 3 小时。显然,显式实例化并没有我预期的那么好。它现在应该可以作为变量实例化:coliru.stacked-crooked.com/a/1e32536b2d6c2561 还添加了缺少的构造函数
  • @TingL:在那里,我弄清楚了为什么它没有完全实例化,并添加了大量的测试。现在应该可以隐约通过了:coliru.stacked-crooked.com/a/ac49da47af600c15
【解决方案2】:

没有标准库(不是“STL”)实现会为此烦恼,因为它有你提到的缺点,而优点不是std::deque 要求的一部分。

从各种操作的算法复杂性到迭代器失效规则,这些要求都是经过精心构建的。以这样一种方式实现容器没有任何好处,以至于没有人可以依赖该实现的好处。

C++ 委员会能否在未来的标准中引入一个具有不同名称和不同约束的新容器,哪些供应商可以按照您的描述实现?是的,他们可以。

【讨论】:

猜你喜欢
  • 2011-08-09
  • 1970-01-01
  • 2017-01-12
  • 2016-06-04
  • 2013-03-10
  • 2021-10-12
  • 2012-03-27
  • 2016-08-18
相关资源
最近更新 更多