【发布时间】:2019-07-20 13:50:42
【问题描述】:
我的队友正在为安全关键型应用程序编写一个固定大小的 std::vector 实现。我们不允许使用堆分配,所以他们创建了一个简单的数组包装器,如下所示:
template <typename T, size_t NUM_ITEMS>
class Vector
{
public:
void push_back(const T& val);
...more vector methods
private:
// Internal storage
T storage_[NUM_ITEMS];
...implementation
};
我们在这个实现中遇到的一个问题是它需要元素存在默认构造函数(这不是std::vector 的要求,并且造成了移植困难)。我决定破解他们的实现,使其表现得更像std::vector 并想出了这个:
template <typename T, size_t NUM_ITEMS>
class Vector
{
public:
void push_back(const T& val);
...more vector methods
private:
// Internal storage
typedef T StorageType[NUM_ITEMS];
alignas(T) char storage_[NUM_ITEMS * sizeof(T)];
// Get correctly typed array reference
StorageType& get_storage() { return reinterpret_cast<T(&)[NUM_ITEMS]>(storage_); }
const StorageType& get_storage() const { return reinterpret_cast<const T(&)[NUM_ITEMS]>(storage_); }
};
然后我可以搜索并用get_storage() 替换storage_,一切正常。 push_back 的示例实现可能如下所示:
template <typename T, size_t NUM_ITEMS>
void Vector<T, NUM_ITEMS>::push_back(const T& val)
{
get_storage()[size_++] = val;
}
事实上,它工作得如此简单,以至于让我开始思考.. 这是对reinterpret_cast 的良好/安全使用吗?直接在上面的代码是放置 new 的合适替代方案,还是存在与复制/移动分配给未初始化对象相关的风险?
编辑:针对 NathanOliver 的评论,我应该补充一点,我们不能使用 STL,因为我们不能为我们的目标环境编译它,也不能证明它。
【问题讨论】:
-
get_storage()[size_++] = val;不合法。该位置没有实际对象,因此您无法分配给它。您需要使用placement new 在正确的位置构造对象。为了让你的生活更轻松,为什么不直接使用std::vector并给它一个堆栈分配器,它有一个固定大小的缓冲区,它提供了要使用的向量? -
@NathanOliver 我们无法使用 STL,因为我们无法为我们的目标环境验证或编译它。我同意在这里放置 new 是一种更好的方法,但应该注意的是,现有代码使用三个独立的编译器构建并通过了严格的单元测试。
-
@AdamBurroughs 那么你错过了一些重要的单元测试,因为容器从不创建或销毁它声称拥有的对象。最简单的方法是,在析构函数有副作用的地方使用
T,并在非空Vector<T,N>的生命周期结束时预期这些副作用。 -
您使用的是什么类型的T?如果不使用placement new,你实际上不会创建任何对象,如果它们在你的UB土地上不是微不足道的
-
@aschepler -- 这是一个很好的观点,但是 AFAIK,placement new 并不能解决这个问题。无论哪种方式都需要手动销毁。
标签: c++ arrays vector reference reinterpret-cast