Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why should all iterators / iterator adaptors not-movable in C++11?

In this question discussed When to make a type non-movable in C++11 and I discovered Scott Meyers had similar question on comp.std.c++, where SG listed below class types are not movable in C++11 libeary.

  • all mutex types(recursive_mutex , timed_mutex, recursive_timed_mutex,
  • condition_variable
  • type_info
  • error_category
  • locale::facet
  • random_device
  • seed_seq
  • reference_wrapper
  • duration
  • time_point
  • - all iterators / iterator adaptors
  • ios_base
  • basic_istream::sentry
  • basic_ostream::sentry
  • all atomic types
  • once_flag

The question is why is all iterators / iterator adaptors not-movable ?

like image 202
billz Avatar asked Jan 14 '13 06:01

billz


People also ask

What is a move iterator?

std::move_iteratorIf this iterator is used as an input iterator, the effect is that the values are moved from, rather than copied from.

How can we avoid iterator invalidation?

To avoid invalidation of references to elements you can use a std::deque if you do not insert or erase in the middle. To avoid invalidation of iterators you can use a std::list.

What causes iterator invalidation?

Iterator Invalidation in C++ When the container to which an Iterator points changes shape internally, i.e. when elements are moved from one position to another, and the initial iterator still points to the old invalid location, then it is called Iterator invalidation. One should be careful while using iterators in C++.

What invalidates an iterator?

An Iterator becomes invalidate when the container it points to changes its shape internally i.e. move elements from one location to another and the initial iterator still points to old invalid location. Iterator invalidation in vector happens when, An element is inserted to vector at any location.


4 Answers

That post, from a year before the standard was ratified, is outdated. The poster is Daniel Krügler, an active committee member, and it's a bit of political lobbying:

These aren't moveable and probably some more by accident, because the rules for implicitly generated move operations became clarified just at the Pittsburgh meeting. A general library issue has been opened

http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#1331

to cope for lack of move support in the library. Be sure that you contact you national body representative, because I've heart that this issue is not a sufficient replacement for a national body comment against the FCD.

In other words, all those types being non-movable would be a showstopper bug for the standard, and he wants readers in the Usenet audience to demand that the problem be fixed before the standard becomes official.

The defect has been moved to the "closed" list. The resolution is (link provided for convenience):

Review the library portion of the spec and incorporate the newly added core feature Move Special Member Functions (N3044).

Since N3044 is a hefty bit of material, it's easy to see why it would be essential for such basic functionality to work.

Iterators, and anything else with simple value semantics like std::duration and std::time_point, are certainly movable. As others have mentioned, copyability implies movability, and if it didn't the language would be broken. This post wasn't wrong at the time; rather it's arguing about the brokenness of the unfinished language.

like image 147
Potatoswatter Avatar answered Nov 15 '22 18:11

Potatoswatter


I take it you include: "classes which implement moving as ordinary copying" in your list of non-moveable types. . Iterators are considered lightweight objects which are cheap to copy. It wouldn't make sense to mandate a move operator for them. E.g. std::vector<T>::iterator essentially is just a wrapped T*, and copying them is just as cheap as moving.

like image 21
MSalters Avatar answered Nov 15 '22 18:11

MSalters


By "not movable" with respect to iterators you probably mean that the class definition does not contain user-declared move operations. But iterators are still copyable. So, a move request still works and falls back on copying. Because of that, there is no reason to provide move operations in situations where the move operations would do exactly the same as the copy operations. For a typical iterator implementation there is nothing to optimize for w.r.t. moving.

like image 32
sellibitze Avatar answered Nov 15 '22 19:11

sellibitze


Short answer

because they are copyable.

Long Answer

We have first to clarify what6 "move" actually means. Von Newman machines don't move data: thy just "copy". Data are copyed form a memory location to another. never "moved".

But at higher abstraction level data can be just pointers to other data. When you copy a pointer nullifying the copyed one, the referred data are said to be "moved" from one "owner" to another.

More generally, an operation that copies a value (lake the address contained in a pointer is) and destroys the original one setting it to a recognizable "invalid" is said to be a "move".

In term of C++ whe can distinguish different type of objects:

  1. The ones containing just a simple value.
  2. The ones containing just a simple pointer or reference that "own" what they refer
  3. The ones containing just a simple pointer or reference not "owing" what they refer
  4. The ones containing huge values.
  5. The ones that represent a physical entity or an operating system (more general "hosting platform") entity.

For all of this types, the concept of "copy" and "move" may have different semantic meaning, and for some of then one operation or the other may have mo meaning at all.

Now Consider type 1 objects:

int a=5; c=0;
c = a;
c = std::move(a);

what do you expect the value of a to be after the move? What about c = a+b? Should a and b be "moved" into operator+ ?

Consider now type 2 objects:

std::unique_ptr<int> pa(new int(5)), pb;
pb = std::move(pa);

Here there are two smart pointer (both of them will be destroyed on scope exit) and only one integer. There is an operation (the delete, in this case) that can be done only once, so only one pointer must retain the "ownership" of the integer. Thsi is the case where "copy" is meaningless, and move is the only supported operation

Now consider type 3 objects:

std::list<int> lst = { 1,2,3,4 };
auto i = lst.begin();
auto j = i; 
*j = *i+5;
++i;
*i = *j;

This makes perfectly sense: it just make the list to become { 6,6,3,4 }. Iterators don't own what they refer: there may be many of them all referring the same value. Copy makes sense, but move doesn't: if we move i into j (instad of copy) no *i and ++i wold be anymore possible.

Now consider objects of type 4:

class A
{
   int m[15000000]; //15 million integers
public:
   int& operator[](unsigned x) { return m[x]; }
   const int& operator[](unsigned x) const { return m[x]; }
};

Such a huge beast will be problematic to be allocated on the stack in the most of the systems. It will most likely leave on the heap, and owned / referred by (smart) pointers. Its address will be moved between its pointers, but the object itself will not be movable. It may still be copyable.

There is another subtle case: when A is itself a pointer to the dynamically allocated huge array: This is the same as std::vector: It is movable since it is itself a "smart pointer" who owns the dynamically allocated data, but may be also copyable, since there may be the case you need a new different copy of the owned data.

Now cosider type 5:

class window
{
private:
   HWND handle;
public:
   window() :handle(CreateWindow(....)) 
   { .... }
   ~window() { DestroyWindow(handle); }
};

Here an instance of window represents a window existing on screen. What does it mean "copy" or "move"?

This is most likely the case for mutex, condition_variable, etc., where both copy and move are disabled.

like image 28
Emilio Garavaglia Avatar answered Nov 15 '22 18:11

Emilio Garavaglia