【问题标题】:How to avoid std::vector to copy on (re-)allocation?如何避免 std::vector 在(重新)分配时复制?
【发布时间】:2015-10-28 17:41:31
【问题描述】:

我在向std::vector 添加新元素时偶然发现了一个问题。

似乎当您尝试向其添加更多元素时,它需要分配更多空间,它通过复制当前拥有的 last element all elements 来做到这一点。这似乎假设向量中的任何元素都是完全有效的,因此复制总是会成功。

在我们的例子中,这不一定是真的。目前我们可能在向量中有一些剩余的元素,因为我们选择不删除它们,它们是有效的对象,但它们的数据并不能保证有效的行为。对象有保护,但我从未考虑向复制构造函数添加保护,因为我认为我们永远不会复制无效对象(向量强制):

CopiedClass::CopiedClass(const CopiedClass& other)
    : member(other.member)
{
    member->DoSomething();
}

碰巧当我们处理完原始对象并将其留在向量中时“成员”为空,因此当 std::vector 尝试复制它时,它会崩溃。

是否可以防止std::vector 复制该元素?还是我们必须防止可能的无效对象被复制?理想情况下,我们希望继续假设只创建了有效对象,但这似乎意味着我们会立即从向量中清除它们,而不是等待并在稍后的某个阶段进行。

【问题讨论】:

  • 那么您有一个对象在其生命周期内可能变得不可复制?
  • std::vector 总是在push_back() 上复制,不,你不能阻止这种行为。
  • @πάνταῥεῖ 我不认为这部分是问题所在。我认为问题在于向量经过重新分配时的副本。
  • 在受影响的复制构造函数中通过vector_member(filter_garbage(other.vector_member)) 初始化向量(并在赋值运算符中执行类似操作(可能是复制/交换))
  • “似乎当您尝试向其添加更多元素时,它需要分配更多空间,它通过复制它当前持有的最后一个元素来做到这一点”。这样的行为将违背标准和常识。你能演示一下吗?

标签: c++ c++11 vector


【解决方案1】:

这实际上是您的 CopiedClass 中的一个设计缺陷,需要修复。

std::vector 的行为符合预期并记录在案。

从你展示的小代码

member->DoSomething();

这表示您将获取指向任何内容的指针的浅表副本。

在我们的例子中,这不一定是真的。目前我们可能在向量中有一些剩余的元素,因为我们选择不删除它们,它们是有效的对象,但它们的数据并不能保证有效的行为。


碰巧当我们处理完原始对象并将其留在向量中时“成员”为空,因此当 std::vector 尝试复制它时,它会崩溃。

由于您的复制构造函数没有正确处理关于遵循Rule of Three 的情况,您的CopiedClass 设计是错误的。

您应该注意创建member 的深层副本,或者使用smart pointer(或普通实例成员),而不是原始指针。

智能指针应该正确地管理这些成员。

对于上面的代码 sn-p,您应该在盲目取消引用和调用 DoSomething() 之前测试 nullpointer

是否可以防止std::vector 复制该元素?

当您要求 时,这是可能的,但要求您永远不要更改向量的大小,并为CopiedClass 提供move constructorassignment operator

否则,std::vectorexplicitly requires 可复制类型(至少对于某些操作而言):

T必须满足CopyAssignableCopyConstructible的要求。

正确满足这些要求是您的责任。


...它通过复制它当前持有的最后一个元素来做到这一点。

另请注意:在需要调整矢量大小的情况下,所有现有元素将被复制,而不仅仅是最后一个。 p>

【讨论】:

  • 自 C++11 起,可复制类型的要求仅适用于某些操作。否则,std::vector<std::unique_ptr<X>> 是不可能的。
  • @ChristianHackl 无论如何,这不是向量行为的问题,而是这里包含的类的错误设计。
  • 当然。它应该被重构。 (顺便说一句,我没有投反对票。)
  • @ChristianHackl 我也没有得到反对票(从不怀疑是你心爱的邻居)。人们似乎不明白我在这里写的东西。
  • 非常全面的答案。你是绝对正确的,这是一个设计缺陷和一个糟糕的假设。我肯定对​​为什么要复制前一个元素感到更加好奇和困惑,这当然是分配更大内存块并移动它的向量当前数据结束...回到数据结构基础知识...谢谢大家
【解决方案2】:

如果您事先知道向量的最大大小,那么针对此问题的解决方法 将调用具有该最大大小的reserve。当向量的容量足够大时,不会发生重新分配,也不需要复制现有元素。

但这实际上只是一种解决方法。您应该重新设计您的类,以使复制不会突然变成无效操作,方法是使复制安全或禁止复制,或者将其替换为移动,例如std::unique_ptr。如果您使复制的有效性依赖于某些运行时状态,那么您甚至不需要std::vector 就会遇到麻烦。一个简单的CopiedClass get(); 将是一个潜在的错误。

【讨论】:

    【解决方案3】:

    似乎当您尝试向其添加更多元素时,它需要 分配更多空间,它通过复制它的最后一个元素来做到这一点 目前持有。这似乎假设向量中的任何元素 完全有效,因此副本将始终成功。

    这两个句子都不是真的正确。当向量空间不足时,是的,它会执行重新分配,但在所有元素中,这些元素会被复制到新位置或可能被移动。当您push_backemplace_back 并且需要重新分配时,通常会复制该元素:如果在此期间抛出异常,它将被视为您从未添加过该元素。如果vector 检测到noexcept 用户定义的移动构造函数(MoveInsertable 概念),则移动元素;如果这个构造函数不是noexcept,并且抛出异常,结果是未指定的。

    对象有守卫,但我从来没有考虑过给对象添加守卫 复制构造函数,因为我认为我们永远不会复制无效的 对象(哪个向量力):

    vector 复制它的value_type:它不关心它包含 的内容是有效还是无效。 应该在您的复制控制方法中注意这一点,其范围正是定义对象的传递方式。


    CopiedClass::CopiedClass(const CopiedClass& other)
        : member(other.member)
    {
        member->DoSomething();
    }
    

    显而易见的解决方案是检查member 是否有效并据此采取行动。

    if (member.internalData.isValid())
        member->DoSomething()
    // acknowledge this.member's data is invalid
    

    我们不知道member 是如何表示的,但Boost.Optional 是您可能正在寻找的。​​p>

    是否可以防止 std::vector 复制该元素?

    重新分配是vector预期提交的,所以,不,你不能。 reserv-ing 空间可以避免这种情况,但是维护代码来处理它并不是真的很轻松。相反,更喜欢std::forward_list/std::list 这样的容器。

    其他解决方案:

    • 保持unique_ptr<decltype(member)>,但指针通常不是真正的解决方案
    • 为您的对象定义移动语义,如其他答案中所述。

    【讨论】:

      猜你喜欢
      • 2020-10-07
      • 2014-06-23
      • 2012-07-21
      • 1970-01-01
      • 2020-10-11
      • 1970-01-01
      • 2018-01-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多