std::vector 的包装器
替换其 erase() 函数
引言
std::vector
的用户通常只在向量的末尾插入和删除元素,分别使用 push_back
和 pop_back
。但是,如果我们需要删除或替换任何元素呢?虽然 vector
提供了一个 erase
函数,但它会向上移动所有后续项目来填充空出的槽位,这可能非常低效。
背景
TcpIoThread
在 Robust 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日:初始版本