Skip to content

array move operator= is actually a copy #95

Open
@kfsone

Description

@kfsone

array &operator=(array &&other) noexcept
{
if (&other != this) {
data_ = other.data_;
owned_ = other.owned_;
other.data_ = nullptr;
other.owned_ = false;
}

The move-assignment operator is not actually moving but copying.

#include <iostream>
struct S {
  S(char *s) : s_(s) {}
  S(S&& rhs) : s_(rhs.s_) { rhs.s_ = nullptr; }
  S& operator=(S&& rhs) { s_ = rhs.s_; return *this; }
  ~S() { *s_ = 0; s_ = nullptr; }
  char *s_;
};

int main() {
  char word1[] = { "hello" };
  char word2[] = { "hello" };

  S s1(word1);
  std::cout << "s1.s_ = '" << s1.s_ << "'\n";
  s1 = std::move(S(word2));
  std::cout << "s1.s_ = '" << s1.s_ << "'\n";
}

https://gcc.godbolt.org/z/az44M1577

Program stdout
s1.s_ = 'hello'
s1.s_ = ''

Move should be implemented as swap or exchange:

  S(S&& rhs) : 
    s_(std::exchange(rhs.s_, nullptr))
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
    {}
  S& operator=(S&& rhs) {
    std::swap(s_, rhs.s_);
    //^^^^^^^^^^^^^^^^^^//
    return *this;
  }

https://gcc.godbolt.org/z/5fzrhaGvd

  array(array &&other) noexcept 
    // if other == this, somehow, we'll take data_, replace it with nullptr,
    // and then replace that with the save of data_, etc.
    : data_(std::exchange(other.data_, nullptr))
    , owned_(std::exchange(other.owned_, false))
  {
  }

  array &operator=(array &&other) noexcept
  {
    // transfer whatever we owned previously to the rvalue, so it can clean
    // up any data we previously had.
    //   {
    //     array one(...);
    //     array two(...);
    //     ...
    //     two = std::move(one);  // two's data transferred to one, which the
    //                            // compiler may now destruct here or later.
    //     ...
    //     one = array(...);      // what two allocated is moved to the temporary,
    //                            // and then destructed here in rvalue::~array()
    //     ...
    //   } // << whatever is still in two destructed here
    // this also eliminates the need for the potentially branch-inducing self check.
    std::swap(data_, other.data_);
    std::swap(owned_, other.owned_);

    return *this;
  }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions