我来晚了,提供一份额外的答案,因为我认为当前没有完全正确的答案。
问题:
一个被移动的vector是否总是为空?
回答:
通常情况下是空的,但并不总是空的。
具体细节:
vector
没有像某些类型一样定义标准化的被移动状态(例如,unique_ptr
在被移动后等于nullptr
),但是vector
的要求并不多。
答案取决于我们是谈论vector
的移动构造函数还是移动赋值运算符。在后一种情况下,答案还取决于vector
的分配器。
vector<T, A>::vector(vector&& v)
这个操作必须具有常数复杂度。这意味着没有其他选择,只能从v
中窃取资源来构建*this
,使v
处于空状态。无论分配器A
是什么,类型T
是什么,都是如此。
所以对于移动构造函数,是的,被移动的vector
将始终为空。虽然这并没有直接说明,但这是由于复杂度要求和没有其他实现方式导致的。
vector<T, A>&
vector<T, A>::operator=(vector&& v)
要求翻译为中文:
这里的内容相对复杂。有3个主要情况:
第一种情况:
allocator_traits<A>::propagate_on_container_move_assignment::value == true
(propagate_on_container_move_assignment
评估为true_type
)
在这种情况下,移动赋值运算符将销毁*this
中的所有元素,使用*this
的分配器释放容量,移动分配器,然后将内存缓冲区的所有权从v
转移给*this
。除了销毁*this
中的元素外,这是O(1)复杂度操作。通常情况下(例如在大多数但不是所有的std::algorithms中),移动赋值的左侧在移动赋值之前具有empty() == true
。
注意:在C++11中,std::allocator
的propagate_on_container_move_assignment
为false_type
,但在C++1y(y == 4 we hope)中已更改为true_type
。
在第一种情况下,移出的vector
将始终为空。
二:
allocator_traits<A>::propagate_on_container_move_assignment::value == false
&& get_allocator() == v.get_allocator()
(propagate_on_container_move_assignment
的返回值为false_type
,且这两个分配器相等)
在这种情况下,移动赋值运算符的行为类似于第一种情况,但有以下例外:
- 分配器不会被移动分配。
- 此情况与第三种情况之间的决策发生在运行时,而第三种情况需要更多的
T
,因此第二种情况也需要更多的T
,即使第二种情况实际上并没有执行T
的额外要求。
在第二种情况下,移动后的vector
将始终为空。
第三种情况:
allocator_traits<A>::propagate_on_container_move_assignment::value == false
&& get_allocator() != v.get_allocator()
(
propagate_on_container_move_assignment
评估为
false_type
,且两个分配器不相等)
在这种情况下,实现无法移动分配器,也无法将任何资源从
v
转移到
*this
(资源是内存缓冲区)。在这种情况下,实现移动赋值运算符的唯一方法是有效地:
typedef move_iterator<iterator> Ip;
assign(Ip(v.begin()), Ip(v.end()));
也就是说,将v
中的每个单独的T
移动到*this
中。如果*this
中有可用的capacity
和size
,则assign
可以重复使用它们。例如,如果*this
与v
具有相同的size
,则实现可以将v
中的每个T
移动分配给*this
。这需要T
是MoveAssignable
的。请注意,MoveAssignable
不需要T
具有移动赋值运算符。复制赋值运算符也可以满足要求。MoveAssignable
只是意味着T
必须可以从rvalue T
中进行分配。
如果
*this
的
size
不足够,则需要在其中构造新的
T
。这要求
T
是可
MoveInsertable
的。就我所知,对于任何明智的分配器,
MoveInsertable
归结为与
MoveConstructible
相同的事情,这意味着可以用右值
T
构造它(并不意味着存在
T
的移动构造函数)。
第三种情况中,移后的
vector
通常不会为空。它可能被移动了元素。如果这些元素没有移动构造函数,则这可能等效于复制赋值。但是,并没有强制要求这样做。实现者可以自由地执行
v.clear()
,使
v
为空。我不知道有哪个实现这样做,也不知道任何这样做的动机。但我没有看到任何禁止它的东西。
David Rodríguez报告说,在这种情况下,GCC 4.8.1调用v.clear()
,导致v
为空。libc++不会这样做,因此v
不为空。两个实现都是符合规范的。
class vector<T> { private: T* m_data; size_type m_size; size_type m_capacity; bool m_this_owns_data; };
。 - ascheplervector
与另一个使用相同T的vector
的存储别名都是非法的。 - Puppy