A moved C++ container and MSVC++

Pages: 12
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <vector>

int main()
{
   std::vector vec1 { 1, 2, 3, 4, 5 };

   std::cout << "vec1 size: " << vec1.size() << "\n\n";

   std::vector vec2 { std::move(vec1) };

   std::cout << "vec1 size: " << vec1.size() << '\n';
   std::cout << "vec2 size: " << vec2.size() << '\n';
}
vec1 size: 5

vec1 size: 0
vec2 size: 5

M'ok, the output of vec1 at line 8 is understandable. Shouldn't line 12 cause a thrown access violation since the vector no longer exists? Is the output at line 12 UB?

::confused::
Last edited on
Shouldn't line 12 cause an illegal access violation since the vector no longer exists?
The move constructor doesn't affect the lifetime of the moved-from object at all.

(As a reminder, the lifetime of a object with class type, like vec1, ends when its destructor begins. The move constructor doesn't call vec1's destructor.)

Is the output at line 12 UB?
By convention, a move assignment or move constructor leaves the moved-from object in an unspecified but valid state. Therefore you can do anything to a moved-from object as long as you don't assume anything about it.

For example, if line 12 was vec1.front(), that would be undefined behavior because front assumes the vector has at least one element. But vec1.size() is fine because size doesn't assume anything.

Finally, unspecified but valid does not always mean empty, but the standard library does actually empty out stuff that's moved from. This can occasionally make it easier to reuse objects:

1
2
3
4
5
6
7
8
9
10
std::vector<std::vector<int>> matrix;
std::vector<int> row_vector; 
for (int i = 0; i < 3; ++i)
{
  for (int j = 0; j < 10; ++j)
    row_vector.push_back(i*j);
  matrix.push_back(std::move(row_vector));

  // row_vector.clear() // redundant because row_vector is already empty here 
}
Last edited on
the standard library does actually empty out stuff that's moved from

Be careful when relying on the state of moved-from objects that are not of move-only types because if the move falls back to copy (e.g. because the object you try to move from is accessed through const ref) the code will still compile and the object that you intended to move from will remain unaffected.

Also be careful with making assumptions based on particular implementations. It seems like the standard doesn't explicitly say that the state of a moved-from std::vector should be empty but some claim they can draw that conclusion based on other requirements (at least in any sane implementation).

https://stackoverflow.com/questions/17730689/is-a-moved-from-vector-always-empty
Last edited on
Don't assume anything about a moved-from object other than it is a valid object of the type. Just because say std::vector behaves in a certain way in one compiler version doesn't mean it will work the same in another. It's likely it will - but don't assume this. Also you can't assume anything else about a non-standard container. Just because std::vector behaves in one way, doesn't mean that myvector also behaves this way. A moved-from object is taken to have a 'temporary' value which either isn't needed any more or is going to be overwritten. Don't use a std::move() if you use the object subsequently.
M'ok, the output of vec1 at line 8 is understandable. Shouldn't line 12 cause a thrown access violation since the vector no longer exists?

As others have pointed out, moving from an object does not destroy that object.

Anyway, even if you allocate an object on the heap (e.g. via new operator), then destroy that object (e.g. via delete operator) and finally try to access the "dangling" pointer, there is absolutely no guarantee that you'll get access violation. It's very possible that it just happens to "work", or that you'll get some "garbage" data...
Last edited on
Well, at least Intellisense throws up a warning, eventually, of potential problems with that moved vector:
Warning	C26800	Use of a moved from object: ''vec1''

Now the programmer needs to examine why they are dinking around with a moved object.

Grey areas, I don't like 'em ;)
Reusing a vector that had its elements moved appears to be acceptable since the object is still valid:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <vector>

int main()
{
   std::vector vec1 { 1, 2, 3, 4, 5 };

   std::cout << "vec1 size: " << vec1.size() << "\n\n";

   std::vector vec2 { std::move(vec1) };

   std::cout << "vec1 size: " << vec1.size() << '\n';
   std::cout << "vec2 size: " << vec2.size() << "\n\n";

   vec1 = { 5, 4, 3, 2, 1 };

   std::cout << "vec1 size: " << vec1.size() << '\n';
}
What grey area? move semantics are well known. Bottom line - don't use a moved-from object after the move.

There's even a whole book on the topic:

https://leanpub.com/cppmove


I'd call "unspecified but valid state" as being a grey area.
And I do own that eBook, I simply haven't gotten around to taking time to read it. I've been reading up on C++20.
The reason why I asked the question(s) is I was using the example from Rainer Grimm's "The C++ Standard" book (it was using std::set, not std::vector) in MSVC++. The full example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>
#include <set>
#include <utility>

int main(){

  std::cout << std::endl;
  
  std::set<int> set1{0, 1, 2, 3, 4, 5};
  std::set<int> set2{6, 7, 8, 9};
  for (auto s: set1) std::cout << s << " ";
  std::cout << " ----- ";
  for (auto s: set2) std::cout << s << " ";
  
  std::cout << std::endl;
  
  set1= set2;
  for (auto s: set1) std::cout << s << " ";
  std::cout << " ----- ";
  for (auto s: set2) std::cout << s << " ";
  
  std::cout << std::endl;
  
  set1= std::move(set2);
  for (auto s: set1) std::cout << s << " ";
  std::cout << " ----- ";
  for (auto s: set2) std::cout << s << " ";
  
  std::cout <<  std::endl;
  
  set2={60, 70, 80, 90};
  for (auto s: set1) std::cout << s << " ";
  std::cout << " ----- ";
  for (auto s: set2) std::cout << s << " ";
  
  std::cout << std::endl;
  
  std::swap(set1, set2);
  for (auto s: set1) std::cout << s << " ";
  std::cout << " ----- ";
  for (auto s: set2) std::cout << s << " ";

  std::cout << "\n\n";

}

When MSVC++ Intellisense whinged about the use, that made me take notice and question why the moved object was being used. Nothing in the accompanying text mentions using a moved object is a possible problem. I had thought using a moved object wasn't the best idea -- IOWs, don't use it in that state -- but then I'm not a C++ expert like Rainer Grimm is.

Admittedly the book isn't a "for beginner's" book, Herr Grimm states in the beginning the book is more of a reference.

Being self-taught I know there are gaps in my understanding of what is and isn't good practice. The gaps are probably quite large crevasses.
Last edited on
Also be careful with making assumptions based on particular implementations. It seems like the standard doesn't explicitly say that the state of a moved-from std::vector should be empty [...]

I thought this was guaranteed by the standard, but it doesn't appear to be.
Now I have some defects to fix.

See https://wg21.link/n3241 which explains that the requirements for standard library types are stronger than the requirements for user-defined types passed into the standard library. The requirements for std types are intended as a "best practice", and produce the valid and unspecified state that we've been talking about.

In contrast the requirements on user-defined types passed to std are the barest minimum needed for the generic code to work. As seeplus points out, user-defined types might not be in a valid state after being moved. For instance a linked list's move constructor may move the sentinel node from the original list without allocating a replacement. This produces a state that's sometimes called "emptier than empty".

So in the program just above, line 27 might actually print stuff, but it never produces undefined behavior.

Thanks, Peter87 & seeplus!
Last edited on
When MSVC++ Intellisense whinged about the use


VS is quite correct. It's warning about use of a moved object after the move which is basically a no-no. In this case it's for test purposes so OK here as just displaying the data.
@mbozzi, that latest snippet with MSVC++ does have some output from the moved object on line 27, nothing gets visibly displayed though.

@mbozzi, that latest snippet with MSVC++ does have some output from the moved object on line 27, nothing gets visibly displayed though.

Can't reproduce, the debugger shows that nothing is printed on line 27 (meaning operator<< never gets called).
I tried all 3 mainstream compilers and 3 standard libraries with the same results.

What am I getting wrong?

IIUC the move constructor could change the values of any elements that are left behind, so printing junk would still be compliant, if very unlikely.
Last edited on
I'd say you are getting nothing wrong. The code as written using a moved object is technically legitimate apparently, though not good practice. :)

I get blank output from line 27, the surrounding output is visibly displayed, so what is actually happening I can't say. I never thought to use the debugger. :)

Essentially line 27 is treated as a null statement.
I think technically the only thing that a moved-from object needs to support is having its destructor called. But I think it's quite reasonable to at least be able to assign to an object that has been moved from. Lots of standard functions (e.g. std::swap) assumes it can do this.
Last edited on
I get blank output from line 27


That is what would be 'expected' (but not mandated). After L24, set2 could be empty as a valid state (almost certainly is for almost all implementations).


But I think it's quite reasonable to at least be able to assign to an object that has been moved from


As specified by the standard in that the moved-from object must be in a valid state after the move. Any valid non-const object that supports assignment can be assigned to.

seeplus wrote:
As specified by the standard in that the moved-from object must be in a valid state after the move.

That's the default requirement that the standard places on standard library types if nothing else is mentioned.

https://eel.is/c++draft/lib.types.movedfrom

But as far as I know there is no such requirement on non-standard types.


seeplus wrote:
Any valid non-const object that supports assignment can be assigned to.

Does the standard require this? I'm not allowed to put preconditions on the assignment operator of my own class, saying that the object must not be in a moved-from state?


std::swap requires the type to fulfil the requirements of Cpp17MoveConstructible and Cpp17MoveAssignable.

https://eel.is/c++draft/utility.arg.requirements#tab:cpp17.moveconstructible

Cpp17MoveConstructible and Cpp17MoveAssignable say the state of the moved-from object rv is unspecified but they also have a note saying:
rv must still meet the requirements of the library component that is using it.
The operations listed in those requirements must work as specified whether rv has been moved from or not.

I guess that means that the requirements that std::swap puts on its arguments, that they should be move constructable and move assignable, must still be true after being moved-from. So it seems like it's more of a requirement of std::swap than a general requirement that we are all forced to follow everywhere (although it's probably a very good idea to do so).
Last edited on
But as far as I know there is no such requirement on non-standard types.


Any 3rd party non-standard types that support move semantics that don't leave the object in a valid state are almost certainly going to get stamped upon from a very great height...
Pages: 12