1、拷贝控制操作
- 拷贝构造函数
- 拷贝赋值运算符
- 移动构造函数
- 移动赋值运算符
- 析构函数
2、只要我们显示的定义了一个构造函数,编译器就不会替我们再定义任何形式的构造函数
class A
{
public:
A(int a):x(a){}
void set(int a) { x = a; }
int get() const { return x; }
private:
int x;
};
void test()
{
A a; //错误 没有默认构造函数
A a2(3); //正确
}
但是拷贝构造不同,即使我们定义了其他形式的拷贝构造,编译器也会为我们合成一个拷贝构造
2、直接初始化与拷贝初始化
string dots(10, '.'); //直接初始化
string s(dots); //直接初始化
string s2 = dots; //拷贝初始化
string s3 = "hello"; //拷贝初始化
string s4 = string(3, 4); //拷贝初始化
当使用直接初始化时,实际上是要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数。当我们使用拷贝初始化时,要求编译器将右侧运算对象拷贝到正在创建的对象中,如果需要的话还要进行类型转换。
如果一个类有移动构造的话,拷贝初始化有时会使用移动构造而非拷贝构造。
拷贝初始化不仅在我们用=定义变量时会发生,在下列情况下也会发生
- 将一个对象作为实参传递给一个非引用类型的实参
- 从一个返回类型为非引用类型的函数返回一个对象
- 用花括号列表初始化一个数组中的元素或一个聚合类中的成员
当我们初始化标准库容器或调用其insert或push成员时,容器会对其元素进行拷贝初始化,与之相对,用emplace成员创建的元素都进行直接初始化。
3、析构的顺序
构造函数是先按照成员在类中出现的顺序对他们进行初始化,然后执行函数体。析构正好相反,先执行函数体,再销毁成员。
4、如果一个类需要自定义析构函数,几乎可以肯定它也需要自定义拷贝赋值运算符和拷贝构造函数。我是这么理解的。需要自定义的析构函数,说明你需要手动释放一些东西,而这些东西一般都是动态分配的,默认的拷贝构造和拷贝赋值运算符并不会在发生拷贝的时候进行动态分配。所以第一句话成立。
5、使用=default来显示要求编译器生成合成的版本
class A
{
public:
A() = default;
A(const A&) = default;
A&am