0

I just found an error in my code due to using an std::vector after having moved it. The code was something like :

std::vector<SomeObject> v1;
//fill v1;
this->v2=std::move(v1);
for(unsigned int i=0; i<v1.size(); i++)
{
    //the code in the for loop was not executed
}

The behavior is not surprising (the state of v1 is undefined, as long as it is valid; so an empty vector seems a logical choice).

My question is : is it possible to make g++ give me a warning when using a variable after moving it? If so, what flag should I add?

NB : I suppose it is not possible to guarantee the detection of such warning (it would, I think, be equivalent to the halting problem, that can't be solved for the general case). But it would already be very useful if I get a warning either for simple cases like the example (use after move can be proven), or if I get a warning when g++ can't prove that it is safe.

Thanks a lot in advance

Sandro
  • 117
  • 4
  • State of `v1` is not undefined, it's guaranteed to be empty. – Yksisarvinen Jun 07 '22 at 13:50
  • @Yksisarvinen Are you sure? I thought the only guarantee was that `v1` would be destructable and assignable. – john Jun 07 '22 at 13:51
  • 3
    @john every well-behaved object has to make the promises that you list, but the standard library makes stronger promises for many of its types. Moved-from types like `std::vector` or `std::thread` or `std::unique_ptr` are guaranteed to be _empty_. – Drew Dormann Jun 07 '22 at 13:52
  • @DrewDormann cppreference states *Unless otherwise specified, all standard library objects that have been moved from are placed in a "valid but unspecified state"*. Where can I find the details? – john Jun 07 '22 at 13:58
  • @DrewDormann The guarantee only applies to the move constructor. The assignment operator makes no such guarantee. – NathanOliver Jun 07 '22 at 14:00
  • 1
    @NathanOliver would you agree with [Howard Hinnant's answer here](https://stackoverflow.com/a/17735913/16287)? That the moved-from vector must be empty after assignment, unless allocators differ and forbid propagation? – Drew Dormann Jun 07 '22 at 14:04
  • @DrewDormann I'll agree that more then likely it will be empty, but the standard doesn't always guarantee it and in Howards answer case 3 happens always in C++11. – NathanOliver Jun 07 '22 at 14:20
  • @NathanOliver [cppreference](https://en.cppreference.com/w/cpp/memory/allocator) suggests that LWG 2103 was applied to C++11 as well? I don't understand how to read LWGs, so I'm not sure if this is a mistake in cppreference page or if I'm reading LWG wrong. – Yksisarvinen Jun 07 '22 at 14:34
  • @Yksisarvinen Ah, neat. Didn't know they had a DR for that. Yes, it should have been backported. – NathanOliver Jun 07 '22 at 14:36
  • @NathanOliver I'm not sure if it's really backported, that was my question :) [LWG 2103](https://cplusplus.github.io/LWG/issue2103) has status "C++14", and [C++11 standard](https://timsong-cpp.github.io/cppwp/n3337/default.allocator) doesn't seem updated either (unless [this source](https://github.com/timsong-cpp/cppwp) is not very viable, not sure). – Yksisarvinen Jun 07 '22 at 14:52
  • 1
    @Yksisarvinen AFAIK DR or normally back ported to older standards. generally it will only happen to the current and newer versions of the compiler, but some do release updates for older versions. – NathanOliver Jun 07 '22 at 14:55

2 Answers2

2

I don't think GCC has a diagnostic for that.

However the Clang static analyzer and Clang-tidy do. If you run them (both) on such code with all diagnostics enabled you get (excerpt):

<source>:7:34: warning: 'v1' used after it was moved [bugprone-use-after-move,hicpp-invalid-access-moved]
    for (unsigned int i = 0; i < v1.size(); i++) {
                                 ^
<source>:6:15: note: move occurred here
    auto v2 = std::move(v1);

/*...*/

<source>:7:34: warning: Method called on moved-from object 'v1' of type 'std::vector' [clang-analyzer-cplusplus.Move]
    for (unsigned int i = 0; i < v1.size(); i++) {
                                 ^~~~~~~~~
<source>:6:15: note: Object 'v1' of type 'std::vector' is left in a valid but unspecified state after move
    auto v2 = std::move(v1);
              ^~~~~~~~~~~~~
<source>:7:34: note: Method called on moved-from object 'v1' of type 'std::vector'
    for (unsigned int i = 0; i < v1.size(); i++) {
                                 ^~~~~~~~~

See godbolt. The first being the clang-tidy diagnostic and the second being the static analyzer diagnostic.

Other linters and static analyzers probably have similar diagnostics implemented. GCC also now has a static analyzer (enabled with -fanalyzer), but it doesn't seem to have a diagnostic for this implemented yet.


Such diagnostics may however easily be false-positives (which may be a reason that the compilers don't implement them as direct compiler warnings).

For example std::unique_ptr has a very clear behavior when moved from. The resulting state is that of an empty std::unique_ptr. If it is then later checked for whether it is empty or not, there is no issue.

Also, objects of all well-behaved types can be reused after a move. The state immediately after the move may simply be unspecified. But e.g. assigning a new state should be fine.

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • From the output of you godbolt link, It is the static analyzer (clang-tidy) that is detecting use-after-move, not the compiler. See here for the compiler to detect this (clang): https://stackoverflow.com/a/74250567/225186 – alfC Oct 31 '22 at 16:02
0

I do not believe this is possible in general. The move semantic does not specify the state of an object that was moved from. That depends on the implementation.

For example a very reasonable implementation for the move assignment of vector is to swap the data between the two vectors. So in you code example the v1.size() would be the size of v2 from before the move assignment. Who is to say the class does not specify that semantic? It would be perfectly reasonable to use-after-move.

Apparently in the STL the move constructor leaves the moved from container empty. But that would still have a perfectly valid v1.size()of 0.

Every moved-from object has some methods that are valid to use. At a minimum assignment should always be allowed. For STL containers I expect size() to also be valid in all cases but maybe not return what you expect. But really how is the compiler supposed to know which methods are save to call on moved-from objects?

For the compiler to give sensical warnings I think the compiler (or C++) needs to introduce some new attributes and the STL needs to annotate it's move constructors / move assignment. That way the compiler could know if a move constructor / assignment follows a certain semantic beyond valid but unspecified state.

Goswin von Brederlow
  • 11,875
  • 2
  • 24
  • 42
  • You argumentation has to mistakes: First cland(d) has such an analysis, so it is possible and at least for some people reasonable and second you mention that the STL should be annotated to make such warning useful, but in fact it is already, because a move has exact that meaning. – gerum Jun 07 '22 at 16:38
  • @gerum Yes, it does have warnings. And as you can see from the examples the warning is wrong, since vector swaps data on move assignment and size() is perfectly fine to call. Other than it being implementation defined there is nothing wrong with the method call. And no, move does not have that meaning. Move leaves things unspecified. Which doesn't mean an implementation can't specify it. Plus the annotation is for other methods to signal what their behavior is after a move. Not for the move construct/assign itself. – Goswin von Brederlow Jun 07 '22 at 16:51