Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best C++ move constructor implementation practice

I am trying to understand the implementation of move-constructor. We all know if we need to manage resources in a C++ class, we need to implement Rule of five (C++ programming).

Microsoft gives us an example: https://msdn.microsoft.com/en-us/library/dd293665.aspx

Here is better one, which uses copy-swap to avoid code duplication: Dynamically allocating an array of objects

     // C++11
     A(A&& src) noexcept
         : mSize(0)
         , mArray(NULL)
     {
         // Can we write src.swap(*this);
         // or (*this).swap(src);
         (*this) = std::move(src); // Implements in terms of assignment
     }

In the move-constructor, directly:

         // Can we write src.swap(*this);
         // or (*this).swap(src);

Because I think (*this) = std::move(src) is a little more complicated. Because if we write as (*this) = src inadvertently, it would call normal assignment operator instead of the move-assignment-operator.

Apart from this question, in Microsoft's example, they wrote code like this: in move-assignment-operator, do we need to check self-assignment? Is it possible to happen?

// Move assignment operator.
MemoryBlock& operator=(MemoryBlock&& other)
{
   std::cout << "In operator=(MemoryBlock&&). length = " 
             << other._length << "." << std::endl;

   if (this != &other)
   {
      // Free the existing resource.
      delete[] _data;

      // Copy the data pointer and its length from the 
      // source object.
      _data = other._data;
      _length = other._length;

      // Release the data pointer from the source object so that
      // the destructor does not free the memory multiple times.
      other._data = nullptr;
      other._length = 0;
   }
   return *this;
}
like image 415
Dongguo Avatar asked Apr 04 '16 14:04

Dongguo


2 Answers

One way is to implement the default constructor, copy constructor and swap function.

And then implement the move constructor, copy and move assignment operators using the first three.

E.g.:

struct X
{
    X();
    X(X const&);
    void swap(X&) noexcept;

    X(X&& b)
        : X() // delegate to the default constructor
    {
        b.swap(*this);
    }

    // Note that this operator implements both copy and move assignments.
    // It accepts its argument by value, which invokes the appropriate (copy or move) constructor.
    X& operator=(X b) {
        b.swap(*this);
        return *this;
    }
};

If you have been using this idiom in C++98, then once you add the move constructor you get the move assignment without writing a single line of code.

In some cases this idiom may be not the most efficient. Because the copy operator always first constructs a temporary and then swaps with it. By hand coding the assignment operators it may be possible to get better performance. When in doubt, check optimized assembly output and use a profiler.

like image 196
Maxim Egorushkin Avatar answered Oct 21 '22 23:10

Maxim Egorushkin


I'm also looking over the Internet to find the best way to implement move constructor and move assignment. There are a few approaches but neither are perfect.

Following are my findings so far.

Here is a Test class I'm using as an example:

class Test {
private:
  std::string name_;
  void*       handle_ = nullptr;

public:
  Test(std::string name)
    : name_(std::move(name))
    , handle_(malloc(128))
  {
  }
    
  ~Test()
  {
    if(handle_) free(handle_);
  } 

  Test(Test&& other) noexcept;             // we are going to implement it
  Test& operator=(Test&& other) noexcept;  // we are going to implement it

  void swap(Test& v) noexcept
  {
    std::swap(this->handle_, v.handle_);
    std::swap(this->name_, v.name_);
  }

private:
  friend void swap(Test& v1, Test& v2) noexcept
  {
    v1.swap(v2);
  }
};

Approach #1: Straight'n'forward

Test::Test(Test&& other) noexcept
  : handle_(std::exchange(other.handle_, nullptr))
  , name_(std::move(other.name_))
{
}

Test& Test::operator=(Test&& other) noexcept
{
  if(handle_) free(handle_);
      
  handle_ = std::exchange(other.handle_, nullptr);
  name_ = std::move(other.name_);

  return *this;
}

Pros

  • The best performance

Cons

  • Code duplication in move constructor and move assignment
  • Partially the code of destructor is duplicated in the move assignment

Approach #2: Destruction + Construction

Test::Test(Test&& other) noexcept
  : handle_(std::exchange(other.handle_, nullptr))
  , name_(std::move(other.name_))
{
}

Test& Test::operator=(Test&& other) noexcept
{
  this->~Test();
  new (this) Test(std::move(other));

  return *this;
}

Pros

  • No code duplication
  • Good performance in case if there are no virtual functions

Cons

  • Virtual Methods Table (VMT) is initialied twice (if class has virtual functions)
  • Cannot be used in the base class. Base class must implement move constructor only.

Approach #3: Copy'n'Swap

Test::Test(Test&& other) noexcept
  : handle_(std::exchange(other.handle_, nullptr))
  , name_(std::move(other.name_))
{
}

Test& Test::operator=(Test&& other) noexcept
{
  Test (std::move(other)).swap(*this);
  return *this;
}

or copy and move operators 2-in-1:

Test& Test::operator=(Test other) noexcept
{
  swap(other, *this);
  return *this;
}

Pros

  • No code duplication

Cons

  • Extra object is created
  • Extra smaller objects created while swapping data members inside the swap function
  • Some kind of code duplication in the swap function

Approach #4: Move constructor via move-assignment

That is what you @Dongguo have found on MSDN

Test::Test(Test&& other) noexcept
{
  *this = std::move(other);
}

Test& Test::operator=(Test&& other) noexcept
{
  if(handle_) free(handle_);
      
  handle_ = std::exchange(other.handle_, nullptr);
  name_ = std::move(other.name_);

  return *this;
}

Pros

  • No code duplication

Cons

  • Does not work for classes that contain the non-default constructible data members.
  • Data members are initialized twice in the move constructor

Links

  • [Wiki] - Move assignment operator
  • [cppreference.com] - Move assignment operator
  • [MSDN] - Move Constructors and Move Assignment Operators
  • [Vollmann] - C++: More on Implementing Move Assignment
  • [Herb Sutter] - Move, simply

More answers

  • Implementing Move Constructor by Calling Move Assignment Operator
  • What is the copy-and-swap idiom?
  • Move constructor vs. Move assignment
like image 34
Dmytro Ovdiienko Avatar answered Oct 22 '22 00:10

Dmytro Ovdiienko