重复使用已移动的容器?

105

重用已移动容器的正确方法是什么?

std::vector<int> container;
container.push_back(1);
auto container2 = std::move(container);

// ver1: Do nothing
//container2.clear(); // ver2: "Reset"
container = std::vector<int>() // ver3: Reinitialize

container.push_back(2);
assert(container.size() == 1 && container.front() == 2);

根据我在 C++0x 标准草案中所了解的,ver3 似乎是正确的方式,因为移动后的对象处于一种

"除非另有规定,这些被移动的对象将处于有效但未指定状态。"

我从未发现过任何"另有规定"的情况。

虽然我觉得 ver3 有点绕弯子,而且更喜欢 ver1,但 vec3 可以允许一些额外的优化,但另一方面容易出错。

我的假设是正确的吗?


5
您可以直接调用clear方法,因为它没有先决条件(因此不依赖于对象的状态)。 - Nicol Bolas
10
@Ben: 我认为那会违背“valid but unspecified”的“valid”部分。 - ildjarn
1
@ildjarn:我认为它只是意味着可以安全地运行析构函数。 - Ben Voigt
@Ben :我认为这意味着必须满足所有类不变式,但对我来说,这显然是一个模糊的领域。 - ildjarn
@ronag:在标准中,容器概述中概述了不变量,而每个成员函数下面列出了前置条件(标记为“要求:”)。 - ildjarn
显示剩余3条评论
3个回答

117
§17.3.26     有效但未指定的状态     [defns.valid] 一个对象的状态没有具体指定,但是对象的不变量得到满足,并且对该对象的操作按照其类型的规定进行。
[ 例子:如果一个类型为std::vector的对象x处于有效但未指定的状态,可以无条件地调用x.empty()函数,并且只有当x.empty()返回false时才能调用x.front()函数。— 结束例子 ]
因此,该对象是可用的。您可以执行任何不需要前置条件的操作(除非您首先验证前置条件)。
例如,clear函数没有前置条件。它会将对象返回到已知状态。所以只需清空它并正常使用即可。

2
在标准文档的哪里可以阅读有关例如std::vector方法的“前置条件”? - ronag
1
@ronag:§23.2 包含列出这些表格的表格。 - Grizzly
3
我发现以下内容很有趣,http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3241.html,他们写道“容器可以比空还要更空”。 - ronag
4
1)如果容器处于有效状态,则调用clear是有效的。 2)虽然容器曾经处于未指定状态,但是调用clear将容器置于指定状态,因为它在标准(§23.2.3表格100)中强制实施了后置条件。 std :: vector <T>具有一个类不变量,即push_back()始终有效(只要TCopyInsertable)。 - ildjarn
3
@ronag在引用"比空还空"这句话的国家评论时,引述的评论是错误的。N3241并没有提出这样的状态。如果一个std::container的实现确实有一个由move操作导致的"比空还空"状态,那么该状态必须是有效的(也就是说,您可以对该对象执行任何不需要先决条件的操作)。 - Howard Hinnant
显示剩余5条评论

21
对象处于“有效但未指定状态”基本上意味着虽然对象的确切状态不能保证,但它是有效的,因此成员函数(或非成员函数)可以保证在不依赖于对象具有特定状态的情况下正常工作。
clear() 成员函数对对象的状态没有任何前提条件(当然,除了对象是有效的),因此可以在已移动的对象上调用。另一方面,例如 front() 函数依赖于容器不为空,因此不能调用该函数,因为无法保证容器非空。
因此,ver2 和 ver3 都应该是可行的。

一个向量通常是空的,但这并不适用于一般情况(例如数组)。 - Mooing Duck
“一个向量始终为空”,你基于什么来得出这个结论? - ronag
1
@ronag:我当然是指版本2和版本3(从文本中应该很清楚,已经修正了那个错别字)。 - Grizzly
有趣的是,front() 的前置条件仅适用于 std::array,即使在表格中也没有说明。 - Ben Voigt
1
@Ben:§23.2.3表100指出front()的操作语义为*a.begin(),§23.2.1/6说:“如果容器为空,则begin() == end()”,而§24.2.1/5则说:“库从不假定超过末尾的值是可解引用的”。因此,我认为可以推断出front()的前提条件,尽管可能需要更明确地说明。 - ildjarn

-10

我认为您无法对已移动的对象执行任何操作(除了销毁它)。

难道您不能使用 swap,以获取移动的所有优势,但使容器保持在已知状态吗?


+1. 交换是个好主意,但并不适用于所有情况,例如使用auto将无法工作。也许一个内部使用交换的safe_move可能是个好主意? - ronag
6
这是一个实时对象,你可以使用任何没有前置条件的功能(除了不变量)。 - Mooing Duck
std::swap 的主要模板有 2 个移动赋值,这些赋值的目标是被移动的值。对我来说,这算是“对一个已移动对象进行操作”。 - Caleth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接