移动向量始终为空? [英] Is a moved-from vector always empty?

查看:119
本文介绍了移动向量始终为空?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



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 ,两个分配器比较等于)



在这种情况下,操作符的行为与案例一类似,但有以下例外:


  1. 分配器未分配移动。

  2. 这种情况和情况之间的决定三发生在运行时,情况三需要更多的 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:

  1. The allocators are not move assigned.
  2. 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 on T.

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆