DragonGamer
DragonGamer

Reputation: 900

Safe to std:move a member?

Have found comparable questions but not exactly with such a case. Take the following code for example:

#include <iostream>
#include <string>
#include <vector>

struct Inner
{
    int a, b;
};

struct Outer
{
    Inner inner;    
};


std::vector<Inner> vec;

int main()
{
    Outer * an_outer = new Outer;

    vec.push_back(std::move(an_outer->inner));

    delete an_outer;
}

Is this safe? Even if those were polymorphic classes or ones with custom destructors?

My concern regards the instance of "Outer" which has a member variable "inner" moved away. From what I learned, moved things should not be touched anymore. However does that include the delete call that is applied to outer and would technically call delete on inner as well (and thus "touch" it)?

Upvotes: 0

Views: 1910

Answers (2)

Asteroids With Wings
Asteroids With Wings

Reputation: 17454

Neither std::move, nor move semantics more generally, have any effect on the object model. They don't stop objects from existing, nor prevent you from using those objects in the future.

What they do is ask to borrow encapsulated resources from the thing you're "moving from". For example, a vector, which directly only stores a pointer some dynamically-allocated data: the concept of ownership of that data can be "stolen" by simply copying that pointer then telling the vector to null the pointer and never have anything to do with that data again. It's yielded. The data belongs to you now. You have the last pointer to it that exists in the universe.

All of this is achieved simply by a bunch of hacks. The first is std::move, which just casts your vector expression to vector&&, so when you pass the result of it to a construction or assignment operation, the version that takes vector&& (the move constructor, or move-assignment operator) is triggered instead of the one that takes const vector&, and that version performs the steps necessary to do what I described in the previous paragraph.

(For other types that we make, we traditionally keep following that pattern, because that's how we can have nice things and persuade people to use our libraries.)

But then you can still use the vector! You can "touch" it. What exactly you can do with it is discoverable from the documentation for vector, and this extends to any other moveable type: the constraints emplaced on your usage of a moved-from object depend entirely on its type, and on the decisions made by the person who designed that type.

None of this has any impact on the lifetime of the vector. It still exists, it still takes memory, and it will still be destructed when the time comes. (In this particular example you can actually .clear() it and start again adding data to a new buffer.)

So, even if ints had any sort of concept of this (they don't; they encapsulate no indirectly-stored data, and own no resources; they have no constructors, so they also have no constructors taking int&&), the delete "touch"ing them would be entirely safe. And, more generally, none of this depends on whether the thing you've moved from is a member or not.

More generally, if you had a type T, and an object of that type, and you moved from it, and one of the constraints for T was that you couldn't delete it after moving from it, that would be a bug in T. That would be a serious mistake by the author of T. Your objects all need to be destructible. The mistake could manifest as a compilation failure or, more likely, undefined behaviour, depending on what exactly the bug was.

tl;dr: Yes, this is safe, for several reasons.

Upvotes: 2

midor
midor

Reputation: 5557

std::move is a cast to an rvalue-reference, which primarily changes which constructor/assignment operator overload is chosen. In your example the move-constructor is the default generated move-constructor, which just copies the ints over so nothing happens.

Whether or not this generally safe depends on the way your classes implement move construction/assignment. Assume for example that your class instead held a pointer. You would have to set that to nullptr in the moved-from class to avoid destroying the pointed-to data, if the moved-from class is destroyed.

Because just defining move-semantics is a custom way almost always leads to problems, the rule of five says that if you customize any of:

  • the copy constructor
  • the copy assignment operator
  • the move constructor
  • the move assignment operator
  • the destructor

you should usually customize all to ensure that they behave consistently with the expectations a caller would usually have for your class.

Upvotes: 0

Related Questions