C++中的拷贝、赋值与销毁

1. 拷贝构造函数

如果一个构造函数的第一个参数是该类类型的引用,且其他参数都有默认值,那么这是个拷贝构造函数。

1
2
3
4
class Foo{
Foo();
Foo(const Foo&); //拷贝构造函数
};

1.1 合成拷贝构造函数

​ 如果没有为类定义拷贝构造函数,那么编译器会自动定义一个合成拷贝构造函数。与合成默认构造函数不同(如果定义了其他构造函数,那么编译器就不会定义合成默认构造函数),即使定义了其他的构造函数,编译器还是会自动定义一个合成拷贝构造函数。

​ 合成的拷贝构造函数会将其参数的成员逐个拷贝到正在创建的对象中。编译器从给定对象中将每个非static成员拷贝到正在创建的对象中。

1.2 拷贝初始化

​ 用”=“来初始化的就是拷贝初始化,它将等号右边的初始值复制给等号左边对象。不用等号的就是直接初始化。

​ 如果使用直接初始化,编译器会根据普通函数匹配的原则来选择合适的构造函数来匹配(这里是说构造函数而不是拷贝构造函数,只要是与类名相同的函数都是构造函数)。如果是拷贝赋值,编译器需要将右侧对象拷贝到左侧对象,如果需要的话还要进行类型转换。

1
2
3
4
string dots(10,'.');		  //直接初始化
string s(dots); //直接初始化
string s2=dots; //拷贝初始化
string nines=string(100,'9'); //拷贝初始化

拷贝构造函数的使用情况:

  1. 用等号定义变量的初始值的时候
  2. 将一个对象作为实参传给非引用类型的形参。(这也解释了为什么拷贝构造函数第一个参数是引用类型
  3. 从一个返回类型为非引用类型的函数返回一个对象。
  4. 用花括号列表初始化数组中的元素或者聚合类中的成员。
1
2
3
4
5
6
struct Data{
int val;
string str;
};
//花括号初始化聚合类
Data d={10,'dong'};

2. 拷贝赋值运算符

通过重载赋值运算符可以赋值的行为。

1
2
3
4
5
6
7
8
9
class Foo{
public:
Foo& operator=(const Foo&);
};
Foo& Foo::operator=(const Foo& f)
{
this.ele = f.ele;
return *this;
}

赋值运算符必须定义为成员函数。赋值运算符左侧的对象默认绑定到了this指针上,返回值一般是自身类型的引用。

3. 析构函数

作用:释放对象所使用的资源,并销毁对象的非static数据成员。

调用析构函数的情况:

  1. 变量离开其作用域
  2. 对象被销毁时,其成员被销毁。
  3. 容器被销毁时,其元素被销毁。
  4. 动态分配的对象,被delete的时候。
  5. 对于临时对象,当创建它的表达式结束时销毁。
1
2
string str = string("hello");
// 等号右侧string("hello")创建了一个临时对象。

当指向一个对象的指针或者引用离开其作用域时不会执行析构函数。

4. 析构与拷贝

需要析构函数的时候一般也需要拷贝构造函数和赋值操作符,反之亦然。

5. default

可以将拷贝构造函数显示定义为default,让编译器自动生成合成的拷贝构造函数。

1
2
3
4
5
class Sales_data{
public:
Sales_data() = default;
Sales_data(const Sales_data&) = default;
};

6. 补充

关于拷贝构造函数,还可以通过将其设置成delete函数(删除函数),来阻止拷贝操作,或者定义成私有成员。

还有关于拷贝的内存管理的内容,拷贝与移动赋值的关系等等。

(以后有时间再补充)


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!