注释掉“移动构造函数”和“移动赋值运算符”会导致编译错误

Commenting out `move constructor` and `move assignment operator` makes compilation error

我从 Ten C++11 Features Every C++ Developer Should Use 中获取了以下代码。我想查看带/不带 move constructormove assignment operator 的输出。原始代码编译得很好。但是如果我注释掉这两种方法的实现,它就无法编译并出现错误:

move-1.cpp: In instantiation of ‘Buffer<T>& Buffer<T>::operator=(const Buffer<T>&) [with T = int]’:
move-1.cpp:90:6:   required from here
move-1.cpp:40:17: error: no match for ‘operator=’ (operand types are ‘std::unique_ptr<int [], std::default_delete<int []> >’ and ‘int*’)
         _buffer = _size > 0 ? new T[_size] : nullptr;

编译器:gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.4)

代码如下:

#include <cassert>
#include <iostream>
#include <memory>
#include <string>

template <typename T> class Buffer {
  std::string _name;
  size_t _size;
  std::unique_ptr<T[]> _buffer;

public:
  // default constructor
  Buffer() : _size(16), _buffer(new T[16]) {
    std::cout << "default constructor\n";
  }

  // constructor
  Buffer(const std::string &name, size_t size)
      : _name(name), _size(size), _buffer(new T[size]) {
    std::cout << "param constructor\n";
  }

  // copy constructor
  Buffer(const Buffer &copy)
      : _name(copy._name), _size(copy._size), _buffer(new T[copy._size]) {
    T *source = copy._buffer.get();
    T *dest = _buffer.get();
    std::copy(source, source + copy._size, dest);
    std::cout << "copy constructor\n";
  }

  // copy assignment operator
  Buffer &operator=(const Buffer &copy) {
    if (this != &copy) {
      _name = copy._name;

      if (_size != copy._size) {
        _buffer = nullptr;
        _size = copy._size;
        _buffer = _size > 0 ? new T[_size] : nullptr;
      }

      T *source = copy._buffer.get();
      T *dest = _buffer.get();
      std::copy(source, source + copy._size, dest);
      std::cout << "copy assignment\n";
    }

    return *this;
  }

  // move constructor
  Buffer(Buffer &&temp)
      : _name(std::move(temp._name)), _size(temp._size),
        _buffer(std::move(temp._buffer)) {
    temp._buffer = nullptr;
    temp._size = 0;
    std::cout << "move constructor\n";
  }

  // move assignment operator
  Buffer &operator=(Buffer &&temp) {
    assert(this != &temp); // assert if this is not a temporary

    _buffer = nullptr;
    _size = temp._size;
    _buffer = std::move(temp._buffer);

    _name = std::move(temp._name);

    temp._buffer = nullptr;
    temp._size = 0;
    std::cout << "move assignment\n";

    return *this;
  }
};

template <typename T> Buffer<T> getBuffer(const std::string &name) {
  Buffer<T> b(name, 128);
  return b;
}

int main() {
  Buffer<int> b1;
  Buffer<int> b2("buf2", 64);
  Buffer<int> b3 = b2;
  Buffer<int> b4 = getBuffer<int>("buf4");
  b1 = getBuffer<int>("buf5");
  return 0;
}

main 中,调用了以下 constructors/assignment 运算符:

Buffer<int> b1;                          // default constructor
Buffer<int> b2("buf2", 64);              // constructor
Buffer<int> b3 = b2;                     // copy constructor
Buffer<int> b4 = getBuffer<int>("buf4"); // move constructor
b1 = getBuffer<int>("buf5");             // move assignment operator

删除移动操作后,这将变为:

Buffer<int> b1;                          // default constructor
Buffer<int> b2("buf2", 64);              // constructor
Buffer<int> b3 = b2;                     // copy constructor
Buffer<int> b4 = getBuffer<int>("buf4"); // copy constructor
b1 = getBuffer<int>("buf5");             // copy assignment operator

最后一行 (b1 = getBuffer<int>("buf5");) 暴露了您的复制赋值运算符中的一个问题:您不能使用 operator= 将原始指针分配给 unique_ptr。相反,使用 reset 或构造一个新的 unique_ptr:

// copy assignment operator
Buffer &operator=(const Buffer &copy) {
  if (this != &copy) {
    _name = copy._name;

    if (_size != copy._size) {
      _buffer = nullptr;
      _size = copy._size;
      if (_size > 0) {
        _buffer.reset(new T[_size]);
      }
    }

    T *source = copy._buffer.get();
    T *dest = _buffer.get();
    std::copy(source, source + copy._size, dest);
    std::cout << "copy assignment\n";
  }

  return *this;
}

为了即使在包含移动构造函数时也能捕捉到这一点,我会在 main 中添加一个强制使用复制赋值运算符的新测试,例如:

// ...
b1 = getBuffer<int>("buf5");             // move assignment operator
b2 = b3;                                 // copy assignment operator