20200223

@ShiKaiWi

Plan

60min: CPP 练习拷贝控制

Notes

  • 拷贝赋值运算符需要考虑左边值的析构、右边值的拷贝,以及自我赋值的场景
  • swap 的使用,需要在使用前 using std::sawp;,这样可以让编译器选择最适合的 swap 函数(标准库的和类的方法)。
  • move 的使用需要用 std::move
  • 移动函数如果不标记 noexcept 的话,标准库中的一些算法,可能仍然尝试做 copy。
  • 方法签名的最后可以加上 const 或者 &&/& 这类限定符,来对 this 指针做限定,而 && 就是限定 this 为右值。
  • 一般利用引用传递资源的函数重载形式为:const X& 和 X&&,没有必要定义 const X&& 或者 X&,const X& 用来做拷贝,因此没有必要修改状态,X&& 用来窃取数据,没必要加上 const 限定符。
  • 右值引用本质上还是一个引用,其析构的触发时机仍然是由原始值控制,因此使用右值引用来窃取状态的时候,需要保证右值引用在状态修改了之后,仍然是可析构的。

练习:

#include <iostream>
#include <utility>

int seq = 0;

struct Inner {
  int i;
  Inner() {
    i = seq++;
    std::cout << "Inner is created, i:" << i << std::endl;
  }

  ~Inner() { std::cout << "Inner is destroyed, i:" << i << std::endl; }
};

struct HasPtr {
  std::size_t *use_count;
  Inner *inner;
  HasPtr() {
    inner = new Inner();
    use_count = new std::size_t(1);
  }
  HasPtr(HasPtr &h) {
    inner = h.inner;
    use_count = h.use_count;
    ++*use_count;
    std::cout << "copy-contructor is called, use_count:" << *use_count
              << ", inner i:" << inner->i << std::endl;
  }
  HasPtr(HasPtr &&h) noexcept {
    inner = h.inner;
    use_count = h.use_count;
    ++*use_count;
    std::cout << "copy-contructor(rvalue) is called, use_count:" << *use_count
              << ", inner i:" << inner->i << std::endl;
  }
  int into_inner_i() && { return inner->i; }
  /* HasPtr& operator=(HasPtr rhs) { */
  /* 	swap(*this, rhs); */
  /* 	std::cout << "copy-assignment(no-ref) is called, use_count:" <<
   * *use_count <<", inner i:" << inner->i << std::endl; */
  /* 	return *this; */
  /* } */
  HasPtr &operator=(HasPtr &&rhs) noexcept {
    swap(*this, rhs);
    std::cout << "copy-assignment(rvalue) is called, use_count:" << *use_count
              << ", inner i:" << inner->i << std::endl;
    return *this;
  }
  HasPtr &operator=(const HasPtr &rhs) {
    if (&rhs != this) {
      --*use_count;
      if (*use_count == 0) {
        delete inner;
        delete use_count;
      }
      inner = rhs.inner;
      use_count = rhs.use_count;
      ++*use_count;
    }
    std::cout << "copy-assignment is called, use_count:" << *use_count
              << std::endl;
    return *this;
  }
  ~HasPtr() {
    --*use_count;
    std::cout << "destructor is called, use_count:" << *use_count
              << ", inner i:" << inner->i << std::endl;
    if (*use_count < 1) {
      delete inner;
      delete use_count;
    }
  }
  void swap(HasPtr &lhs, HasPtr &rhs) {
    using std::swap;
    swap(lhs.use_count, rhs.use_count);
    swap(lhs.inner, rhs.inner);
  }
};

void test_reference_count() {
  HasPtr hp1;
  HasPtr hp2(hp1);
  HasPtr hp3;
  hp3 = hp2;
}

void test_move() {
  HasPtr hp1;
  HasPtr hp2;
  hp2 = std::move(hp1);
}

void test_rvalue_qualifier() {
  HasPtr hp;
  std::move(hp).into_inner_i();
}

void consume_rvalue(HasPtr &&h) {
  std::cout << "consume_rvalue starts, inner i:" << h.inner->i << std::endl;
  std::cout << "consume_rvalue ends, inner i:" << h.inner->i << std::endl;
}

void test_rvalue_destruct() {
  HasPtr hp1;
  consume_rvalue(std::move(hp1));
  HasPtr hp2;
}

struct X {
  int i;
  X(int i) : i(i) {}
  /* X(X&&) = delete; */
};

void consume_x(X x) {
  std::cout << "consume_x starts, inner i:" << x.i << std::endl;
  std::cout << "consume_x ends, inner i:" << x.i << std::endl;
}

void test_rvalue_construct_delete() {
  X x(1);
  // compiler will complain if X(&&) == delete is uncommented
  consume_x(std::move(x));
}

int main() {
  test_reference_count();
  test_move();
  test_rvalue_qualifier();
  test_rvalue_destruct();
  test_rvalue_construct_delete();
}

More