65.9K
CodeProject 正在变化。 阅读更多。
Home

std::vector 的包装器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.53/5 (3投票s)

2020年6月12日

GPL3

3分钟阅读

viewsIcon

21476

downloadIcon

154

替换其 erase() 函数

引言

std::vector 的用户通常只在向量的末尾插入和删除元素,分别使用 push_backpop_back。但是,如果我们需要删除或替换任何元素呢?虽然 vector 提供了一个 erase 函数,但它会向上移动所有后续项目来填充空出的槽位,这可能非常低效。

背景

TcpIoThreadRobust Services Core (RSC) 中最初使用原始数组来实现文件描述符(到套接字句柄),该文件描述符传递给 TCP 的 poll 函数。我想看看是否可以使用 std::vector 来代替,此提示描述了出现的代码。

Using the Code

使用 vector 作为套接字描述符的问题是,当 TCP 连接关闭时,任何套接字都可以释放。poll 的实现要求套接字是连续的,没有任何中间无效(空)条目。这可以通过将数组的最后一个套接字移动到从数组中间删除套接字时空出的槽位来实现。但是,vector 不支持此行为。它只提供限制性的 pop_back 和低效的 erase。因此,有必要使用一个新的模板来实现所需的行为,该模板尽可能转发到 vector,但在必要时添加新函数。

如果这个新模板是通用的,它应该支持任何类型的对象,而不仅仅是套接字句柄。因此,它应该保留 pop_back 的语义,pop_back 也会删除正在删除的对象。新的模板还应该允许自定义分配器,与 vector 的方式相同。

我最终将新的模板命名为 Array,这有点缺乏创意。也许你可以提出一个更好的名字。无论如何,让我们看看它的数据和函数。

#include <cstddef>
#include <memory>
#include <utility>
#include <vector>

template<typename T, class A = std::allocator<T>> class Array
{
   //  The maximum size allowed for the array.
   //
   size_t max_;

   //  The array of items.
   //
   std::vector<T, A> vector_;
};

Array 是根据 vector 实现的,但强制了最大长度。如果你不想要限制,你可以删除 max_ 的出现,或者简单地将其设置为 SIZE_MAX

接下来,我们有一些简单的东西

Array() : max_(0) { }

~Array() = default;

Array(const Array& that) = delete;

Array& operator=(const Array& that) = delete;

//  Specifies that the array is limited to MAX elements.
//
void Init(size_t max) { max_ = (max < 2 ? 2 : max); }

在将元素添加到 Array 之前,必须调用 Init 来设置其最大大小。我不需要 Array 可复制,所以我删除了这些函数。如果你需要它们,可以将它们设置为默认值,与析构函数相同。

接下来,我们有一些函数通常只是转发到 vector。但是,它们也强制了最大大小,并使用 RSC 的 Debug.h 来支持函数跟踪工具和带有堆栈跟踪的异常。在此文章中显示的代码中已删除对 Debug.h 的依赖,但在 .zip 文件中已保留。

bool PushBack(T& item)
{
   if(vector_.size() >= max_) return false;
   vector_.push_back(item);
   return true;
}

size_t Size() const { return vector_.size(); }

bool Empty() const { return vector_.empty(); }

const T& Front() const { return vector_.front(); }

T& Front() { return vector_.front(); }

const T& Back() const { return vector_.back(); }

T& Back() { return vector_.back(); }

const T& At(size_t index) const { return vector_.at(index); }

T& At(size_t index) { return vector_.at(index); }

const T& operator[](size_t index) const { return vector_[index]; }

T& operator[](size_t index) { return vector_[index]; }

const T* Data() const
{
   if(vector_.empty()) return nullptr;
   return vector_.data();
}

T* Data()
{
   if(vector_.empty()) return nullptr;
   return vector_.data();
}

bool Reserve(size_t capacity)
{
   if(capacity > max_) return false;
   vector_.reserve(capacity);
   return true;
}

最后,我们得到了新的 Erase 函数,它使用 std::swap,以便可以重用 pop_back 及其删除元素的副作用。请注意,与 vector::erase 不同,这不会保留元素的顺序

//  Erases the item in the cell specified by INDEX and moves
//  the last item into its cell.
//
void Erase(size_t index)
{
   auto size = vector_.size();
   if(index >= size) throw("invalid index for Array.Erase");
   if(index < (size - 1))
   {
      std::swap(vector_[index], vector_.back());
   }
   vector_.pop_back();
}

我们还可以定义一个 Replace 函数。请注意,这会覆盖现有对象,因此如果它有析构函数,请检查它是否具有复制运算符 (operator=)!

//  Replaces the item in the cell specified by INDEX with ITEM.
//
void Replace(size_t index, T& item)
{
   if(&item == nullptr) throw("invalid item for Array.Replace");
   auto size = vector_.size();
   if(index >= size) throw("invalid index for Array.Replace");
   vector_[index] = item;
}

历史

  • 2020 年 6 月 13 日:根据 Mircea 的评论更新 Replace
  • 2020年6月12日:初始版本
© . All rights reserved.