如何避免std :: vector复制(重新)分配?

时间:2015-10-28 17:41:31

标签: c++ c++11 vector

在向其添加新元素时,我偶然发现std::vector的问题。

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

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

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

当我们完成原始对象并将其留在向量中时,“成员”被置为空,所以当std :: vector尝试复制它时,它会崩溃。

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

3 个答案:

答案 0 :(得分:7)

这实际上是CopiedClass中需要修复的设计漏洞。

std::vector表现得如预期和记录。

从您显示的小代码

member->DoSomething();

这表明你将把指针的浅表副本带到任何地方。

  

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

           

当我们完成原始对象并将其留在向量中时,“成员”被置为空,所以当std :: vector尝试复制它时,它会崩溃。

由于您的复制构造函数无法正确处理Rule of Three您的CopiedClass设计错误。

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

智能指针应正确处理这些成员的管理。

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

  

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

正如您要求这样可能,但要求您永远不要更改向量的大小,并为CopiedClass提供move constructorassignment operator。< / p>

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

  

T必须符合CopyAssignableCopyConstructible的要求。

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

  

...它通过复制当前持有的最后一个元素来实现。

另请注意:在需要调整矢量大小的情况下,将复制现有元素的所有,而不仅仅是 last one

答案 1 :(得分:2)

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

但这真的只是一种解决方法。您应该重新设计您的班级,以便复制不会突然变为无效操作,无论是通过安全复制还是禁止复制,或者可能将其替换为移动,例如std::unique_ptr。如果您使复制的有效性取决于某些运行时状态,那么您甚至不需要std::vector来解决问题。一个简单的CopiedClass get();将是一个潜在的错误。

答案 2 :(得分:2)

  

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

这两个句子都不正确。当向量空间不足时,是的,执行重新分配,但是所有元素,这些元素被复制到新位置或可能移动。当您需要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是您可能正在寻找的。

  

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

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

其他解决方案:

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