2

I try to understand the move constructor.

I allocate memory in the class' constructor and destroy it in the destructor.

When I try to move the class, I still have a double free.

#include <algorithm>

class TestClass
{
 public:
  TestClass() {a_ = new int[1];}
  TestClass(TestClass const& other) = delete;
  TestClass(TestClass && other) noexcept // = default;
  {
    this->a_ = std::move(other.a_);
  }
  ~TestClass() {delete[] a_;}
 private:
  int* a_ = nullptr;
};

int main( int argc, char** argv )
{
  TestClass t;
  TestClass t2 = std::move(t);
}

Why std::move do not change to nullptr other.a_ ?

I have the same problem if the move constructor is default.

I found the following questions but I still don't know why the move operator don't change the source variable to default value.

How does std::move invalidates the value of original variable?

C++ how to move object to a nullptr

C++ std::move a pointer

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • 3
    In your move constructor, you forgot `other.a_ = nullptr;`. Or by doing `std::swap(this_>a_, other.a_);`. – Eljay May 12 '20 at 16:42
  • 1
    In C++14 and after, you'd use [`std::exchange`](https://en.cppreference.com/w/cpp/utility/exchange) to initialise `a_` – Caleth May 12 '20 at 16:49
  • I assume this is some kind of training, that is why you are not using `std::vector` which will do this in best possible way. – Marek R May 12 '20 at 16:50
  • Yes, it's a POC. I want to undersand what the default implementation of the move constructor / assignment and to know when I need to use default or custom implementation. – Vincent LE GARREC May 12 '20 at 17:06
  • @VincentLEGARREC `std::move()` is just a type-cast, and you are trying to "move" to a raw pointer, not to a movable type with a move-constructor or move-assignment operator. That is effectively a copy assignment, so there is no code invoked to actually reset the input pointer to `nullptr`. – Remy Lebeau May 12 '20 at 17:51

2 Answers2

6

std::move just produces an rvalue (xvalue); it won't perform move operation, it won't modify the argument at all.

In particular, std::move produces an xvalue expression that identifies its argument t. It is exactly equivalent to a static_cast to an rvalue reference type.

Given this->a_ = std::move(other.a_);, as built-in type, i.e. int*, this->a_ is just copy-assigned from ohter.a_, then both the pointers point to the same object. The defaulted move constructor does the same thing in fact. (It performs member-wise move operation on the data members; note that for built-in types the effect of move is same as copy.)

You need to set other.a_ to nullptr explicitly if you want to define that after moved the object should contain a null pointer.

E.g.

TestClass(TestClass && other) noexcept
{
  this->a_ = other.a_;
  other.a_ = nullptr;
}
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • Thanks. So, what the difference between the default copy constructor and the default move constructor (except the fact that one use const ref and the other a non-const rvalue) ? – Vincent LE GARREC May 12 '20 at 17:01
  • 1
    @VincentLEGARREC For built-in type data members, they have the same effect. Both class-type data members, they'll call copy-constructor or move-constructor on the data member, then the effect depends on how the data members' constructors are implemented. – songyuanyao May 12 '20 at 17:07
2

First, std::move is just a cast which leads to other.a_ to be treated as an rvalue. For pointers, a move is just a copy.

This is so, I presume, because clearing the source pointer is not necessary in all cases and it would cause overhead in the cases where it's not needed.

You need to do the clearing explicitly.

Or, even simpler, just use std::unique_ptr<int> a_. Then you don't need to define any special member functions and the class behaves as you would imagine.

Mika Fischer
  • 4,058
  • 1
  • 26
  • 28