拷贝控制,当定义一个类时,我们显式或隐式地指定在此类型的对象拷贝、移动、赋值和销毁时做什么。通过以下五种特殊的成员函数来控制这些 *** 作。
- 拷贝构造函数,移动构造函数:用同类型的另一个对象初始化本对象时做什么。
- 拷贝赋值运算符,移动赋值运算符:将一个对象赋予同类型的另一个对象时做什么。
- 析构函数:定义了此类类型对象销毁时做什么。
如果我们没有定义这些拷贝控制成员,编译器会自动定义确实 *** 作。因此很多类,忽略了这些拷贝控制 *** 作。但是,有些类依赖这些 *** 作默认的定义会导致意想不到的灾难。
一、拷贝构造函数class Foo
{
public:
Foo(); // 默认构造函数
Foo(const Foo&); // 拷贝构造函数(注意: 拷贝构造函数) 注意: 拷贝函数的第一个参数必须是引用类型
}
// 一般情况,合成拷贝构造函数会将其参数的成员,逐个拷贝到正在创建的对象中
//(编译器将每个非static成员拷贝到正在创建的对象中)
#include
#include
using namespace std;
class Foo
{
public:
Foo(string str, int num) : a(str), b(num) {} // 构造函数
Foo(const Foo &foo) : a(foo.a), b(foo.b) {} // 拷贝构造函数。(与默认合成的相同,删除效果一样)
private:
string a;
int b;
};
main()
{
Foo m("hello", 1); // 通过构造函数,创建对象m (直接初始化)
Foo n(m); // 通过拷贝构造函数,用对象m初始化对象n (直接初始化)
Foo p = m; // (拷贝初始化)
}
1. 拷贝初始化与直接初始化
- 直接初始化:要求编译器使用普通的函数匹配来选择我们提供的参数最匹配的构造函数。
- 拷贝初始化:要求编译器将右侧运算的对象拷贝到正在创建的对象当中,如果需要的,还要进行类型转换。
注意:拷贝初始化,通常使用拷贝构造函数来完成,如果一个类有一个移动构造函数,则拷贝初始化有时还会使用过移动构造函数而不是拷贝构造函数完成。
2. 拷贝初始化不仅仅在用 = 定义对象时会发生,下面的情况也会发生。- 将一个对象作为实参传递给一个非引用类型的形参
- 从一个返回类型为非引用类型的函数返回一个对象
- 用花括号列表来初始化一个数组中的元素或一个聚合类中的成员。
在函数初始化过程中,具有非引用类型的参数要进行拷贝初始化。当一个函数的返回值是非引用类型时,返回值会被用来初始化调用方的结果。
如果拷贝构造函数的参数为非引用类型。则为了调用拷贝构造函数,我们必须拷贝他的实参,但为了拷贝实参,又需要调用拷贝构造函数,如此无限循环。因此调用永远也不会成功。
4. 拷贝初始化的限制当使用explicit关键字来声明构造函数时,我们只能以直接初始化的使用。并且编译器不会在自动转换过程中使用该构造函数。
vector<int> v1(10); // 正确: 直接初始化
vector<int> v1 = 10; // 错误: 接收大小参数的构造函数时explicit的
5. 编译器可以直接绕过拷贝构造函数
string test1 = "abc"; // 拷贝初始化
string test2("abc"); // 编译器略过了拷贝构造函数。
// 注意,虽然编译器略过了拷贝构造函数,但是在这个程序点上。拷贝/移动构造函数必须是存在且可访问的(例如不能是 private)
参考:《C++ primer》整理笔记
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)