我正在用C ++做自定义向量类。我对这样的代码有疑问:
vector<T> vec;
vec.push_back(one);
vec.push_back(two);
vec.push_back(vec[0]);
push_back的定义如下:
void push_back(const T & v)
避免不必要的复制。它的实现看起来像
if (size == capacity)
{
allocate new storage
copy old values into new storage
// 2
delete old storage
fix pointers and counters
}
// 1
copy v at the end of storage
如果我们想要推送已经在向量中的元素并且向量需要扩展(大小等于其容量),则会出现问题。如果我们这样做(vec.push_back(vec[0])
),那么在// 1
,它已经被取消分配。所以我们需要有一份副本。另一种选择是在扩展期间添加它,某处为// 2
,但这看起来并不漂亮。
你会如何解决这个问题?
答案 0 :(得分:4)
在我看过的一些STL实现中(例如当前的VS2010),他们首先检查指向要添加的新数据项的指针是否在向量缓冲区的当前范围内。
如果是,则找到向量内数据位置的索引位置(不是指针!)。即使重新分配底层缓冲区,这也不会改变。缓冲区扩展后(无论是否涉及实际重新分配),可以从索引位置安全地复制数据项。
我认为您提到的另一种替代方法是在重新分配缓冲区之前采用要添加的数据项的本地(堆栈)副本,以防该项目位于向量内部。显然,如果复制数据类型非常昂贵(可能像其他矢量那么?),这可能不是一个好主意。
希望这有帮助。
答案 1 :(得分:3)
另外需要考虑的是异常安全:如果内存分配或复制对象失败会发生什么?在这种情况下,您应该尝试让您的课程表现得尽可能好。请考虑以下保证:
由于无法控制内存分配器或要复制的对象,因此无法实现最后的操作。然而,使用两阶段方法可以实现“强有力”保证:以不影响可见状态的方式完成“侧面”失败的工作;一旦成功,使用无法失败的操作更新持久状态(例如更新指针和删除旧内存 - 如果删除提供“无投掷”保证,通常应该这样做。)
因此,更安全的异常版本可能如下所示:
if (new_size > capacity) {
allocate new storage, controlled by a local smart pointer
copy old values
copy new values
update state, releasing the smart pointer
delete old storage
} else {
copy new values
}
此处的“else”情况仅提供“弱”保证:如果任何对象无法复制,则可能已复制了一些(但不是全部)新数据。无论是在复杂性方面(提供一种“解除”故障变化的方式)还是速度和内存(使用重新分配版本,无论是否已经有足够的内存),改进都会产生成本。
答案 2 :(得分:2)
我会推迟删除旧存储空间:
if (size == capacity)
{
allocate new storage
copy old values into new storage
fix pointers and counters, but keep one pointer at the old storage
}
copy v at the end of storage
delete old storage