3

In C++ move constructor is required to reset the moved object which to me seems to be a duplication of what the destructor does in most of the cases.

Is it correct that defining a reset-method and using it in both destructor and move-constructor is the best approach? Or maybe there are better ways?

Gill Bates
  • 14,330
  • 23
  • 70
  • 138
  • _"...what the destructor does..."_ nope because the move to object has stolen the contents of the move from object. All you have to do is to leave the moved from object in a valid state (which is specific to your object) and avoid double ownership etc. – Richard Critten Aug 30 '20 at 19:29
  • *"is required to reset the moved object"* The only responsibility a move constructor or move assignment operator has with regards with the moved-from instance is to leave the it both destructible and assignable. Usually, the move constructor and move assignment operator don't free the resources of the moved-from instance, they (as the name implies) moves them to the other instance. If your move constructor and destructor do the same thing, you've made a serious mistake. – François Andrieux Aug 30 '20 at 19:30
  • _"In C++ move constructor is required to reset the moved object"_ — No, it's not. It's just common practice and it very much depends on the semantics of _reset_ and _contents_. Not all the objects own resources. – Daniel Langr Aug 30 '20 at 20:04

2 Answers2

3

Move constructors typically "steal" the resources held by the argument (e.g. pointers to dynamically-allocated objects, file descriptors, TCP sockets, I/O streams, running threads, etc.) rather than make copies of them, and leave the argument in some valid but otherwise indeterminate state. For some types, such as std::unique_ptr, the moved-from state is fully specified.

"Stolen" resources should not be released as this will usually lead to errors. For example, a move constructor which "steals" a pointer has to ensure that the destructor of the moved-from object won't delete the pointer. Otherwise, there will be a double-free. A common way of implementing this is to reset the moved-from pointer to nullptr.

Here is an example:

struct Pointer {
    int *ptr;

    // obtain a ptr resource which we will manage
    Pointer(int* ptr) : ptr{ptr} {}

    // steal another object's ptr resource, assign it to nullptr
    Pointer(Pointer &&moveOf) : ptr{moveOf.ptr} {
        moveOf.ptr = nullptr;
    }

    // make sure that we don't delete a stolen ptr
    ~Pointer() {
        if (ptr != nullptr) {
            delete ptr;
        }
    }
};

Is it correct that defining a reset-method and using it in both destructor and move-constructor is the best approach? Or maybe there are better ways?

This depends on the resource which is managed, but typically the destructor and move-constructor do different things. The move constructor steals the resource, the destructor frees a resource if it hasn't been stolen.

In C++ move constructor is required to reset the moved object which to me seems to be a duplication of what the destructor does in most of the cases.

You are right that there often is duplication of work. This is because C++ does not have destructive move semantics, so the destructor still gets called separately, even when an object has been moved from. In the example I have shown, ~Pointer() still needs to get called, even after a move. This comes with the runtime cost of checking whether ptr == nullptr. An example of a language with destructive move semantics would be Rust.


Related Posts:

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
  • "_However, this behavior should not be relied upon_". For most standard library container types, it can. See for example https://en.cppreference.com/w/cpp/container/vector/vector. For `std::string` there is no such guarantee, since small string optimization is allowed. – juanchopanza Aug 30 '20 at 20:27
2

is required to reset the moved object which to me seems to be a duplication of what the destructor does in most of the cases.

It seems to me that you misunderstand what a destructor is used for in most cases.

The purpose of the "reset" (as you call it) in move is to set the state of the object so that it satisfies the internal pre-conditions of the destructor (more generally, any class invariant). If the constructor didn't do that, then the object couldn't be destroyed, which would be against conventions and good practices and would likely lead to mistakes.

In many cases, the destructor cannot possibly do this same "reset". For example, there is no way to distinguish an invalid pointer from a valid pointer. This is why the move constructor of a smart pointer resets the pointer to null.

Is it correct that defining a reset-method and using it in both destructor and move-constructor is the best approach?

It is unclear when this could be useful. Doesn't seem typical.

eerorika
  • 232,697
  • 12
  • 197
  • 326