在展开期间将向量成员推入向量:vector.push_back(vector[0])

Posted

技术标签:

【中文标题】在展开期间将向量成员推入向量:vector.push_back(vector[0])【英文标题】:Pushing vector member into vector during expand: vector.push_back(vector[0]) 【发布时间】:2011-05-07 10:24:41 【问题描述】:

我正在用 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 的某个地方,但这看起来并不漂亮。

你会如何解决这个问题?

【问题讨论】:

我会在你的//2 做这件事 - 它有什么不好的地方?一个“正常”场景的代码路径,另一个用于异常的代码路径。 在函数结束前不要删除旧存储。 顺便说一句,你为什么不重新分配? realloc() 保证保留旧块的数据,所以如果你使用索引而不是指针,你应该没问题 @drhirsch: realloc 仅在使用 mallocfree 管理内存并且对象被限制为 POD 类型时才可用。 @drhirsh - realloc 只会将数据保留在如果可能。当堆的下一部分已经被占用时,它将重新分配和复制数据(以与非 POD 类型不兼容的方式)。 【参考方案1】:

在我见过的一些 STL 实现中(例如当前的 VS2010),它们首先检查指向要添加的新数据项的指针是否在向量缓冲区的当前范围内。

如果是这样,则找到指向向量内数据位置的索引位置(不是指针!)。即使重新分配底层缓冲区,这也不会改变。一旦缓冲区被扩展(无论这是否涉及实际重新分配),数据项就可以从索引位置安全地复制。

我认为您提到的另一种选择是在重新分配缓冲区之前获取要添加的数据项的本地(堆栈)副本,以防万一该项目位于向量内部。显然,如果复制数据类型非常昂贵(可能像另一个向量??)这可能不是一个好主意。

希望这会有所帮助。

【讨论】:

我正在考虑它,我发现保持索引位置的一个缺陷。考虑struct foo int a, b, c, d; 的向量。 Foo 的长度为 16 个字节。使用强制转换,我可以在((char*)vector_start + 8) 添加对象——使用a = v[0].c, b = v[0].d, c = v[1].a, d = v[1].b。这很粗糙,但可能。 是的,我认为确实很粗糙。我不太了解标准,但是如果允许这种容器的使用,我会感到惊讶。我认为您应该只 push_back() 真正的数据项,而不仅仅是任意字节。像这样的 C 风格转换(顺便说一句,你必须转换回 vector::value_type !)已经破坏了一般 c++ 对象类型的东西,即使它可能对你构建的特殊情况有意义。 【参考方案2】:

要考虑的另一件事是异常安全:如果内存分配或复制对象失败会发生什么?在这种情况下,你应该尽量让你的班级表现得尽可能好。考虑以下保证:

不保证:如果出现问题,对象可能处于无效状态 弱保证:如果出现问题,则对象处于未定义但有效的状态 强力保证:如果出现问题,对象不变 “不投掷”保证:不会出错。

您无法在此处实现最后一个,因为您无法控制内存分配器或要复制的对象。然而,“强”保证可以使用两阶段的方法:以不影响可见状态的方式做可能“侧面”失败的工作;成功后,使用不会失败的操作更新持久状态(例如更新指针和删除旧内存 - 如果删除提供“不抛出”保证,它通常应该这样做)。

因此,更安全的异常版本可能如下所示:

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

这里的“其他”情况仅提供“弱”保证:如果任何对象无法复制,则可能已复制了部分(但不是全部)新数据。改进这将有成本,无论是在复杂性(提供一种“解开”失败更改的方法)或速度和内存(使用重新分配版本,无论是否已经有足够的内存)方面。

【讨论】:

【参考方案3】:

我会推迟删除旧存储:

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

【讨论】:

以上是关于在展开期间将向量成员推入向量:vector.push_back(vector[0])的主要内容,如果未能解决你的问题,请参考以下文章

为啥我不能将整数向量推入 C++ 中的二维整数向量?

将 1D 向量作为行推入 2D 向量数组

将结构向量推入结构?

如何将字符串的字符推入二维向量[关闭]

如何将字符串(逐个字符)推入字符串向量

如何使用循环将向量推入 queue<vector<int>>?