20200223
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();
}