如何避免std::vector在(重新)分配时进行复制

How to avoid std::vector to copy on (re-)allocation?

本文关键字:分配 复制 std 何避免 vector 重新      更新时间:2023-10-16

我刚刚在向std::vector添加新元素时遇到了一个问题。

似乎当你试图向它添加更多的元素,并且它需要分配更多的空间时,它通过复制

最后一个元素 它当前持有的所有元素来做到这一点。这似乎假设向量中的任何元素都是完全有效的,因此复制总是成功的。

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

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

当我们处理完原始对象并将其放在向量中时,"member"恰好为null,所以当std::vector试图复制它时,它会崩溃。

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

它实际上是CopiedClass中的一个需要修复的设计缺陷

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

从你显示的小代码

member->DoSomething();

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

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


当我们处理完原始对象并将其放在向量中时,"member"恰好为null,所以当std::vector试图复制它时,它会崩溃。

由于您的复制构造函数在遵循"三条规则"方面没有正确处理这种情况,因此CopiedClass的设计是错误的。

您应该创建member的深度副本,或者使用智能指针(或普通实例成员),而不是原始指针。

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

同样对于上面的代码片段,在盲目地取消引用和调用DoSomething()之前,您应该针对nullpointer进行测试。

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

当你要求c++11时,这是可能的,但要求你永远不要改变向量的大小,并为CopiedClass提供一个移动构造函数和赋值运算符。

否则,std::vector明确要求可复制类型(至少对于某些操作):

T必须满足CopyAssignable和CopyConstructable的要求。

您有责任正确地满足这些要求。


它通过复制当前保存的最后一个元素来实现这一点。

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

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

但这实际上只是一个变通办法。您应该重新设计您的类,使复制不会突然变成无效操作,无论是通过确保复制安全,还是通过禁止复制,并可能用移动(如std::unique_ptr)代替它。如果您使复制的有效性取决于某些运行时状态,那么您甚至不需要std::vector来遇到麻烦。一个简单的CopiedClass get();将是一个潜在的错误。

当你试图为它添加更多元素时,它似乎需要分配更多的空间,它通过复制它的最后一个元素来实现目前持有。这似乎假设向量中的任何元素是完全有效的,因此复制总是成功的。

这两个句子都不太正确。当矢量空间不足时,它会执行重新分配,但对于,所有元素和这些元素都会被复制到新位置,或者可能会被移动。当您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.可选是您可能想要的。

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

Reallocation是vector期望提交的东西,所以,不,你不能。reserv占用空间可以避免这种情况,但维护代码来处理这种情况并不是很容易。更喜欢std::forward_list/std::list这样的容器。

其他解决方案:

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