第13章 拷贝控制
1. 什么是拷贝构造函数? P440
如果一个类的构造函数的第一个参数是自己类类型的引用, 且所有其他参数都有默认值, 那么这就是一个拷贝构造函数.
2. 拷贝初始化和直接初始化. P441
如果初始化的时候使用等号”=”来初始化一个对象, 那么就是拷贝初始化.
相反, 如果初始化时, 没有用等号”=”, 那么就是直接初始化.
如果是拷贝初始化, 那么用的一定是拷贝构造函数(也有可能用移动构造函数).
如果是直接初始化, 那么就用参数形式对应的构造函数, 但是也有可能用的还是拷贝构造函数.
3. 拷贝构造函数不仅仅在我们用=定义变量时发生, 还发生在:
传递实参对象给非引用形参.
返回对象作为非引用返回值.(但是如果以该返回对象继续初始化一个新对象的话,
这个过程就不会调用拷贝构造函数了)
用花括号列表初始化数组中的一个元素或一个聚合类的成员.
vector等对象用花括号初始化或使用push_back和insert等操作时.
上面b.v本来推测应该是31, 但是却是21. 这里应该是编译器在初始化b的时候主动略过了拷贝构造函数, 所以如果我们想定义一个类的拷贝构造函数,
就正确的定义它的行为.
不要像上述例子一样添加一些本质上不是拷贝的行为. 因为你没法100%保证到底拷贝构造函数能被执行几次…
4. 为什么拷贝构造函数的类对象形参必须是引用类型?
假设拷贝构造函数传递的类对象不是引用了. 现在要将实参B传递给非引用形参A, 那么A要调用自己的拷贝构造函数通过B来构造A. 但是A的拷贝构造函数也需要一个非引用的类对象做形参, 所以
A的拷贝构造函数的形参又开始调用自己的拷贝构造函数通过B来构造自己, … 依次类推, 无限循环.
5. 拷贝赋值运算符. P443
拷贝赋值运算符就是用一个类对象给另一个类对象赋值时的”=”运算符(不是初始化时用的”=”哦).
6. 一个具有指针类型成员的类对象使用合成的拷贝赋值运算符.
可以看到a和b对象的指针成员指向了相同的地址, 那么当它们析构时, 后一个析构的对象将delete一个无效的指针.
7. 使用=default. P449
使用=default可以显示要求编译器为我们合成拷贝控制成员.
8. 阻止拷贝. P449
可以通过在拷贝构造函数和拷贝赋值运算符后面加=delete 来阻止拷贝. 为什么要阻止拷贝? 例如, iostream类阻止了拷贝, 以避免多个对象写入或读取相同的IO缓存.
9. C++11之前,通过将类的拷贝构造函数和拷贝赋值运算符声明为private的且不定义来阻止对该类对象的拷贝(或拷贝初始化).就算是友元也不能拷贝该类型, 因为未定义.
10. 行为像值得类. P453
必须定义自己的: 默认构造函数,
拷贝构造函数,拷贝赋值运算符以及析构函数.
且拷贝赋值运算符必须能处理自赋值.
11. 定义行为像智能指针的类. P455
(下面代码中的A类的拷贝赋值运算符中的第一句 ++(*num)应改为++(*rhs.num))
12. 给值行为的类定义自己的swap函数. P458
13. 左值引用 和 右值引用: P471
左值持久,右值短暂.
变量是左值, 所以不能将一个右值引用绑定到变量上. 就算这个变量本身就是一个右值引用也不行.
14. 移动构造函数与移动赋值运算符. P473
移动构造函数的本质其实就是夺取一个对象的内存资源直接给被构造的对象,且原来对象不能继续控制本来的内存了.
15. 合成的移动操作
P475
只有一个类没有定义任何拷贝控制成员(拷贝构造函数,拷贝赋值运算符,析构函数)时, 且类的所有非static成员都是可移动的,
此时编译器才会给该类合成移动构造函数和移动赋值运算符.
16. 当既有拷贝操作也有移动操作时,使用哪个? P477
一条原则:移动右值,拷贝左值.
即当右边是一个右值时, 就优先使用移动操作(也可以使用拷贝).
当右边是一个左值时, 只能使用拷贝操作(除非使用std::move将其转换)
注意上述get函数返回的结果是一个右值.
且如果我们定义get如下:
A get(A a)
{
return a;
}
那么在传递参数的时候a就会使用一次拷贝构造函数,使得a的值加上了”111”.
17. 如果没有移动操作, 只有拷贝操作, 那么右值也被拷贝. P477
即必要时候, 右值也能被拷贝构造函数和拷贝赋值运算符拷贝.
但是拷贝构造函数和拷贝赋值运算符的参数必须是const类型的引用才行, 如果不是就会出错(可以自己试试看看到底是什么情况).
18. 赋值运算符实现拷贝赋值和移动赋值两种功能P478
如果赋值运算符的形参是传值调用, 那么用实参初始化形参就需要调用拷贝构造函数(实参是左值)或移动构造函数(实参是右值). 那么可以用下面方式实现等价的拷贝赋值和移动赋值.
(注意:下面的程序=操作符内使用的是自定义的swap,
因为标准库的swap需要类支持=操作符,
但是=操作符我们还没定义)
19. 右值和左值引用成员函数 P483
在类的成员函数后面加上& 或&& 可以限定该成员函数只能接受左值或右值的参数.
同样可以避免对右值对象使用=赋值等操作.