移动向量始终为空? [英] Is a moved-from vector always empty?
问题描述
N3485 17.6.5.15 [lib.types.movedfrom] / 1:
在C ++标准库中定义的类型对象可以从(12.8)移动。可以显式指定或隐式生成移动操作
。除非另有说明,否则此类移动对象应置于有效但未指定的状态。
code> vector 明确将其从此段落中排除。然而,我不能想出一个正确的实现,将导致向量不为空。
有一些标准,这意味着我失踪或是类似于处理 basic_string
作为C ++ 03中的连续缓冲区
p>我要来这个晚会,并提供一个额外的答案,因为我不相信任何其他答案在这个时候是完全正确的。
问题:
移入的向量始终为空?
回答:
通常,但不是,不总是。
血腥的细节:
(例如
unique_ptr
在被移动之后被指定为等于 nullptr
), 。但是向量
的要求是没有太多的选项。
答案取决于我们' re谈论向量
的移动构造函数或移动赋值运算符。在后一种情况下,答案也取决于向量
的分配器。
矢量< T,A> :: vector(vector& v)
此操作必须具有不变的复杂性。这意味着没有选项,但是从 v
中窃取资源来构造 * this
,留下 v
处于空状态。这是真的,不管分配器 A
是什么,类型 T
是什么。
所以对于move构造函数,是的,从向量
始终为空。这不是直接指定的,但是不在复杂性要求之内,而且没有其他方法来实现。
向量< 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
中的allocator释放容量,移动分配分配器,然后将内存缓冲区的所有权 v
至 * this
。除了破坏 * this
中的元素,这是一个O(1)复杂度操作。通常(例如在大多数但不是全部std ::算法中),移动赋值之前的lhs具有 empty()== true
。 >
注意:在C ++ 11中 propagate_on_container_move_assignment
for std :: allocator
是 false_type
,但对于C ++ 1y(y == 4),这已更改为 true_type
)。
在第一种情况下,从向量
始终为空。
两个:
allocator_traits< A> :: propagate_on_container_move_assignment :: value == false
&& get_allocator()== v.get_allocator()
( propagate_on_container_move_assignment
计算为 false_type
,两个分配器比较等于)
在这种情况下,操作符的行为与案例一类似,但有以下例外:
- 分配器未分配移动。
- 这种情况和情况之间的决定三发生在运行时,情况三需要更多的
T
,因此情况二,即使情况二不实际上在T
上执行这些额外的要求。
-from 向量
将始终为空。
三:
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()));
也就是说,将每个 T
v
至 * this
。 assign
可以重用 capacity
和 size
c $ c> * this (如果可用)。例如,如果 * 具有与
相同的
实现可以将
T
从 v
移动到 * this
。这需要 T
为 MoveAssignable
。注意, MoveAssignable
不需要 T
具有移动赋值运算符。副本分配操作符也就足够了。 MoveAssignable
只是指 T
必须从右值 T
。
如果 *
的 size
不够,则必须在 * this
中构建新 T
。这需要 T
为 MoveInsertable
。对于任何合理的分配器我可以想到, MoveInsertable
归结为与 MoveConstructible
相同的东西, rvalue T
(不表示 T
的移动构造函数的存在)。
在情况三中,从向量
移动一般不会为空。它可以是充满了移动元素。如果元素没有移动构造函数,这可能等同于副本赋值。然而,没有什么要求这一点。实现者可以自由地做一些额外的工作,并执行 v.clear()
如果他愿意,留下 v
空。我不知道任何实施这样做,也不知道任何动机的实施这样做。
DavidRodríguez报告说GCC 4.8.1调用 v.clear()
在这种情况下,留下 v
为空。 libc ++ 不会, v
不为空。两种实现都符合。
I know that generally the standard places few requirements on the values which have been moved from:
N3485 17.6.5.15 [lib.types.movedfrom]/1:
Objects of types defined in the C++ standard library may be moved from (12.8). Move operations may be explicitly specified or implicitly generated. Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.
I can't find anything about vector
that explicitly excludes it from this paragraph. However, I can't come up with a sane implementation that would result in the vector being not empty.
Is there some standardese that entails this that I'm missing or is this similar to treating basic_string
as a contiguous buffer in C++03?
I'm coming to this party late, and offering an additional answer because I do not believe any other answer at this time is completely correct.
Question:
Is a moved-from vector always empty?
Answer:
Usually, but no, not always.
The gory details:
vector
has no standard-defined moved-from state like some types do (e.g. unique_ptr
is specified to be equal to nullptr
after being moved from). However the requirements for vector
are such that there are not too many options.
The answer depends on whether we're talking about vector
's move constructor or move assignment operator. In the latter case, the answer also depends on the vector
's allocator.
vector<T, A>::vector(vector&& v)
This operation must have constant complexity. That means that there are no options but to steal resources from v
to construct *this
, leaving v
in an empty state. This is true no matter what the allocator A
is, nor what the type T
is.
So for the move constructor, yes, the moved-from vector
will always be empty. This is not directly specified, but falls out of the complexity requirement, and the fact that there is no other way to implement it.
vector<T, A>&
vector<T, A>::operator=(vector&& v)
This is considerably more complicated. There are 3 major cases:
One:
allocator_traits<A>::propagate_on_container_move_assignment::value == true
(propagate_on_container_move_assignment
evaluates to true_type
)
In this case the move assignment operator will destruct all elements in *this
, deallocate capacity using the allocator from *this
, move assign the allocators, and then transfer ownership of the memory buffer from v
to *this
. Except for the destruction of elements in *this
, this is an O(1) complexity operation. And typically (e.g. in most but not all std::algorithms), the lhs of a move assignment has empty() == true
prior to the move assignment.
Note: In C++11 the propagate_on_container_move_assignment
for std::allocator
is false_type
, but this has been changed to true_type
for C++1y (y == 4 we hope).
In case One, the moved-from vector
will always be empty.
Two:
allocator_traits<A>::propagate_on_container_move_assignment::value == false
&& get_allocator() == v.get_allocator()
(propagate_on_container_move_assignment
evaluates to false_type
, and the two allocators compare equal)
In this case, the move assignment operator behaves just like case One, with the following exceptions:
- The allocators are not move assigned.
- The decision between this case and case Three happens at run time, and case Three requires more of
T
, and thus so does case Two, even though case Two doesn't actually execute those extra requirements onT
.
In case Two, the moved-from vector
will always be empty.
Three:
allocator_traits<A>::propagate_on_container_move_assignment::value == false
&& get_allocator() != v.get_allocator()
(propagate_on_container_move_assignment
evaluates to false_type
, and the two allocators do not compare equal)
In this case the implementation can not move assign the allocators, nor can it transfer any resources from v
to *this
(resources being the memory buffer). In this case, the only way to implement the move assignment operator is to effectively:
typedef move_iterator<iterator> Ip;
assign(Ip(v.begin()), Ip(v.end()));
That is, move each individual T
from v
to *this
. The assign
can reuse both capacity
and size
in *this
if available. For example if *this
has the same size
as v
the implementation can move assign each T
from v
to *this
. This requires T
to be MoveAssignable
. Note that MoveAssignable
does not require T
to have a move assignment operator. A copy assignment operator will also suffice. MoveAssignable
just means T
has to be assignable from an rvalue T
.
If the size
of *this
is not sufficient, then new T
will have to be constructed in *this
. This requires T
to be MoveInsertable
. For any sane allocator I can think of, MoveInsertable
boils down to the same thing as MoveConstructible
, which means constructible from an rvalue T
(does not imply the existence of a move constructor for T
).
In case Three, the moved-from vector
will in general not be empty. It could be full of moved-from elements. If the elements don't have a move constructor, this could be equivalent to a copy assignment. However, there is nothing that mandates this. The implementor is free to do some extra work and execute v.clear()
if he so desires, leaving v
empty. I am not aware of any implementation doing so, nor am I aware of any motivation for an implementation to do so. But I don't see anything forbidding it.
David Rodríguez reports that GCC 4.8.1 calls v.clear()
in this case, leaving v
empty. libc++ does not, leaving v
not empty. Both implementations are conforming.
这篇关于移动向量始终为空?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!