【发布时间】:2018-12-26 13:18:46
【问题描述】:
我需要一个具有以下要求的 C++ 容器:
- 容器可以在连续内存中存储不可复制和不可移动的对象。对于
std::vector,对象必须是可复制的或可移动的。 - 容器的
capacity在运行时的构建过程中是已知的,并且在销毁之前是固定的。所有需要的内存空间都是在构建过程中分配的。对于boost::static_vector,容量在编译时是已知的。 - 当
emplace_back容器中有更多元素时,容器的大小会随着时间的推移而增加,但不应超过capacity。 - 由于对象不可复制或移动,因此不允许重新分配。
似乎 STL 和 BOOST 都没有我需要的容器类型。我也在这方面进行了广泛的搜索,但没有找到答案。所以我实现了一个。
#include <memory>
template<class T>
class FixedCapacityVector {
private:
using StorageType = std::aligned_storage_t<sizeof(T), alignof(T)>;
static_assert(sizeof(StorageType) == sizeof(T));
public:
FixedCapacityVector(FixedCapacityVector const&) = delete;
FixedCapacityVector& operator=(FixedCapacityVector const&) = delete;
FixedCapacityVector(size_t capacity = 0):
capacity_{ capacity },
data_{ std::make_unique<StorageType[]>(capacity) }
{ }
~FixedCapacityVector()
{
for (size_t i = 0; i < size_; i++)
reinterpret_cast<T&>(data_[i]).~T();
}
template<class... Args>
T& emplace_back(Args&&... args)
{
if (size_ == capacity_)
throw std::bad_alloc{};
new (&data_[size_]) T{ std::forward<Args>(args)... };
return reinterpret_cast<T&>(data_[size_++]);
}
T& operator[](size_t i)
{ return reinterpret_cast<T&>(data_[i]); }
T const& operator[](size_t i) const
{ return reinterpret_cast<T const&>(data_[i]); }
size_t size() const { return size_; }
size_t capacity() const { return capacity_; }
T* data() { return reinterpret_cast<T*>(data_.get()); }
T const* data() const { return reinterpret_cast<T const*>(data_.get()); }
private:
size_t const capacity_;
std::unique_ptr<StorageType[]> const data_;
size_t size_{ 0 };
};
我的问题是:
- 我为什么要手动执行此操作?我找不到标准容器。或者,也许我没有看对地方?还是因为我想做的不是传统的?
- 手写容器是否正确实现?异常安全、内存安全等怎么样?
【问题讨论】:
-
“对于这种常用的容器类型...”是不正确的。您的不可复制且不可移动的对象可以存储在
std::shared_ptr中,然后将std::shared_ptr存储在std::vector中吗? -
@Eljay 感谢您的评论。然后对象不在连续内存中。实际上这就是程序现在正在做的事情(使用
std::vector<std::unique_ptr>)。或许我不应该说“这么常用的容器类型”。 -
不可复制和不可移动的对象是否需要在连续内存中?
-
为什么不简单地包装一个
std::vector,将其调整为所需的最大值,并跟踪实际使用了多少元素?如果该向量是您的类的私有成员,则无论您的类的用户做什么,您都可以确保它在初始化后永远不会调整大小。毕竟,即使用户向您的类请求emplace_back()某些东西,您的类(或其成员函数的实现)也不需要将其转换为向量的emplace_back()的调用。 -
@Peter 实际上这是我想到的第一件事。但是如果
T既不可复制又不可移动,std::vector<T>将无法编译。