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::allocator是false_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,两个分配器比较相等)
在这种情况下,移动赋值运算符的行为与情况一样,但有以下例外:
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)
也就是说,将每一个人T从v到*this.如果可用,assign可以重用两者capacity和sizein *this.例如,如果*this有相同size的v实施可以将分配给每个T从v到*this.这就要求T要MoveAssignable.请注意,MoveAssignable不需要T移动赋值运算符.复制赋值运算符也足够了. MoveAssignable只是意味着T必须从右值分配T.
如果size的*this不充分,那么新T将在待建*this.这就要求T要MoveInsertable.对于我能想到的任何理智的分配器,MoveInsertable归结为同样的东西MoveConstructible,这意味着可以从rvalue构造T(并不意味着存在移动构造函数T).
在第三种情况下,移动vector通常不会为空.它可能充满了移动元素.如果元素没有移动构造函数,则这可能等同于复制赋值.但是,没有任何要求这样做.实现者可以自由地做一些额外的工作并且v.clear()如果他愿意的话执行,v留空.我不知道有任何实施这样做,我也没有意识到实施的任何动机.但我没有看到任何禁止它的东西.
DavidRodríguez报告说,v.clear()在这种情况下GCC 4.8.1调用,v留空. libc ++没有,v不留空.两种实现都是一致的.
虽然在一般情况下它可能不是一个理智的实现,但移动构造函数/赋值的有效实现只是从源复制数据,保持源不变.此外,对于赋值的情况,move可以实现为swap,而move -from容器可能包含move -to容器的旧值.
如果你像我们一样使用多态分配器,实际上可以实现move作为副本,并且分配器不被认为是对象值的一部分(因此,赋值永远不会改变正在使用的实际分配器).在此上下文中,移动操作可以检测源和目标是否使用相同的分配器.如果它们使用相同的分配器,则移动操作只能从源移动数据.如果它们使用不同的分配器,则目标必须复制源容器.