移动的向量是否总是空的?

Bil*_*eal 36 c++ vector language-lawyer move-semantics c++11

我知道通常标准对已经移动的值的要求很少:

N3485 17.6.5.15 [lib.types.movedfrom]/1:

可以从(12.8)移动C++标准库中定义的类型的对象.可以显式指定或隐式生成移动操作.除非另有规定,否则此类移动物体应置于有效但未指定的状态.

我找不到任何与vector此明确排除的内容.但是,我无法想出一个理智的实现,导致向量不为空.

是否有一些标准需要我缺少或者类似于在C++ 03中作为连续缓冲区处理basic_string

How*_*ant 55

我迟到了这个派对,并提供了一个额外的答案,因为我不认为此时任何其他答案是完全正确的.

题:

移动的向量是否总是空的?

回答:

通常,但不,不总是.

血腥的细节:

vector没有像某些类型那样的标准定义的移动状态(例如,unique_ptr被指定为等于nullptr移动后).但是,要求vector是没有太多选择.

答案取决于我们是在讨论vector移动构造函数还是移动赋值运算符.在后一种情况下,答案还取决于vector分配器.


vector<T, A>::vector(vector&& v)
Run Code Online (Sandbox Code Playgroud)

此操作必须具有持续的复杂性.这意味着没有选择,只能从v构造中窃取资源*this,留下v空状态.无论分配器A是什么,也不是类型,T都是如此.

所以对于move构造函数,是的,move-from vector将始终为空.这不是直接指定的,但是不符合复杂性要求,并且没有其他方法可以实现它.


vector<T, A>&
vector<T, A>::operator=(vector&& v)
Run Code Online (Sandbox Code Playgroud)

这要复杂得多.有3个主要案例:

一:

allocator_traits<A>::propagate_on_container_move_assignment::value == true
Run Code Online (Sandbox Code Playgroud)

(propagate_on_container_move_assignment评估为true_type)

在这种情况下,移动赋值运算符将破坏所有元素*this,使用分配器释放容量*this,移动分配分配器,然后将内存缓冲区的所有权转移v*this.除了元素的破坏之外*this,这是O(1)复杂性操作.并且通常(例如,在大多数但不是所有std ::算法中),移动分配的lhs在移动分配empty() == true之前.

注意:在C++ 11中,propagate_on_container_move_assignmentfor std::allocatorfalse_type,但是已经改为true_typeC++ 1y(我们希望y == 4).

如果是One,则move-from vector将始终为空.

二:

allocator_traits<A>::propagate_on_container_move_assignment::value == false
    && get_allocator() == v.get_allocator()
Run Code Online (Sandbox Code Playgroud)

(propagate_on_container_move_assignment评估为false_type,两个分配器比较相等)

在这种情况下,移动赋值运算符的行为与情况一样,但有以下例外:

  1. 分配器未分配.
  2. 本案例和案例三之间的决定发生在运行时,案例三需要更多T,因此案例二也是如此,即使案例二实际上并没有执行这些额外的要求T.

在案例二中,移动的vector将始终为空.

三:

allocator_traits<A>::propagate_on_container_move_assignment::value == false
    && get_allocator() != v.get_allocator()
Run Code Online (Sandbox Code Playgroud)

(propagate_on_container_move_assignment评估为false_type,并且两个分配器不相等)

在这种情况下的实施不能移动分配分配器,也不可能从传输任何资源v,以*this(资源是所述存储器缓冲区).在这种情况下,实现移动赋值运算符的唯一方法是:

typedef move_iterator<iterator> Ip;
assign(Ip(v.begin()), Ip(v.end()));
Run Code Online (Sandbox Code Playgroud)

也就是说,将每一个人Tv*this.如果可用,assign可以重用两者capacitysizein *this.例如,如果*this有相同sizev实施可以将分配给每个Tv*this.这就要求TMoveAssignable.请注意,MoveAssignable不需要T移动赋值运算符.复制赋值运算符也足够了. MoveAssignable只是意味着T必须从右值分配T.

如果size*this不充分,那么新T将在待建*this.这就要求TMoveInsertable.对于我能想到的任何理智的分配器,MoveInsertable归结为同样的东西MoveConstructible,这意味着可以从rvalue构造T(并不意味着存在移动构造函数T).

在第三种情况下,移动vector通常不会为空.它可能充满了移动元素.如果元素没有移动构造函数,则这可能等同于复制赋值.但是,没有任何要求这样做.实现者可以自由地做一些额外的工作并且v.clear()如果他愿意的话执行,v留空.我不知道有任何实施这样做,我也没有意识到实施的任何动机.但我没有看到任何禁止它的东西.

DavidRodríguez报告说,v.clear()在这种情况下GCC 4.8.1调用,v留空. libc ++没有,v不留空.两种实现都是一致的.

  • 谢谢!和TL; DR:这是可能的,因为它不被禁止,并且库可以自定义. (4认同)
  • 霍华德,我不相信“恒定时间”要求会排除“短向量”“优化”的实现,至少提供元素构造函数和析构函数是微不足道的。只要短向量具有最大大小,复制操作就会受到复制该大小所需的时间的限制,这足以成为常数时间。在这种情况下,即使是移动构造函数也可能不会留下空向量。 (2认同)
  • @rici:[container.requirements.general]/p10/b6要求除非另有说明,否则交换不会使容器的任何迭代器失效.矢量没有另外说明.但是[string.require]/p6/pb1确实为字符串指定,由脚注237.澄清.所有这一切的意图是禁止向量的"短字符串"优化,但允许它们用于字符串. (2认同)
  • *实现者可以自由地执行一些额外的工作并执行`v.clear()`[...]我不知道有任何实现这样做.*GCC 4.8.1就是这样做的. (2认同)

Dav*_*eas 5

虽然在一般情况下它可能不是一个理智的实现,但移动构造函数/赋值的有效实现只是从源复制数据,保持源不变.此外,对于赋值的情况,move可以实现为swap,而move -from容器可能包含move -to容器的旧值.

如果你像我们一样使用多态分配器,实际上可以实现move作为副本,并且分配器不被认为是对象的一部分(因此,赋值永远不会改变正在使用的实际分配器).在此上下文中,移动操作可以检测源和目标是否使用相同的分配器.如果它们使用相同的分配器,则移动操作只能从源移动数据.如果它们使用不同的分配器,则目标必须复制源容器.

  • @DeadMG:这是关于*iterator invalidation*的连续第二条评论,你介意解释一下你的想法吗? (4认同)