bad_alloc 根据右值和对象有效性插入对象

bad_alloc upon inserting object by rvalue and object validity

考虑以下代码(假设 SomeClass 具有移动构造函数和指针等字段,在对象将其内容与另一个对象交换后这些字段将失效):

SomeClass data;
std::list queue;
try { queue.push_back(std::move(data)); }
catch(std::bad_alloc)
{
    data.doThingsUsingObjectData(); //Can I do this here?
    return;
}
data.doThingsUsingObjectData(); //Definitelly can't do this here - object was invalidated

关键是 - 如果在使用 std::move() 将对象添加到 STL 时抛出 bad_alloc,这是否意味着我尝试通过右值添加到 STL 的对象实际上仍然有效并且我可以访问它而不用担心未定义的行为吗?

这取决于抛出异常的原因,但一般来说,不会。对象在移动操作后处于未指定状态。请参考this question.

容器可能使用也可能不使用 SomeClass 的移动构造函数,但是当您使用 std::move 时,您将对象标记为右值引用,您不应该使用对象在使用它(或传递它)作为右值参考后。

作为一种好的做法,如果您考虑重用该对象,则永远不要使用 std::move。

IIRC,标准声明对象从中移出,保持未指定状态。这意味着您可以调用成员方法,只要它们不依赖于对象的特定状态,它们就应该可以正常工作。 需要参考

A std::list 将其元素存储在节点中。当您添加一个元素时,会分配一个新节点,然后将该元素存储在该节点中(通过调用 std::allocator_traits<A>::construct,这通常会执行 placement-new 以在节点内就地构造对象),然后最后,通过将新节点和相邻节点的指针更新为link,将节点添加到列表中。

如果抛出 bad_alloc 异常,则只能是分配新节点或在节点内构造元素(因为更新指针不会抛出任何东西)。

如果在分配节点对象时发生异常,那么很可能对象还没有被移动,因为没有尝试移动构造一个新元素(你不能这样做直到你有一个节点来构建它!)。不能保证容器在构建节点内的最终元素之前不会创建中间变量,但那将是一个质量很差的实现,因为没有理由这样做。

如果 class 有一个非抛出移动构造函数那么你知道异常一定来自节点分配。否则,如果异常来自构造元素,则右值参数的状态将由所讨论的 class 的异常安全保证决定。

所以简而言之,如果 std::list 实现不好,或者被插入的对象在移动构造期间可能抛出异常并且没有强大的异常安全保证,那么右值参数可能会留在一个移出的状态。但除此之外,您应该能够相信它是未修改的。