拷贝控制函数包括:拷贝构造函数、拷贝赋值函数、移动构造函数、移动赋值函数、析构函数。
编译器默认生成的函数:默认构造函数,拷贝构造函数,拷贝赋值函数,析构函数,取地址符,取地址符const。
1.构造函数
如果我们没有定义任何构造函数,编译器会为我们生成一个默认的构造函数。
如果定义了,则没有默认构造函数,即不能以Class item来定义对象了。
因此,不管有没有定义构造函数,最好自己定义下默认构造函数。
2.拷贝构造函数
class Foo{
public:
Foo(){}
Foo(const Foo&); //拷贝构造函数
};
有以下几点说明:
1)第一个参数必须是自身的引用类型,而且几乎总是一个const引用。
这里必须是引用的原因是:拷贝构造函数被用来初始化非引用类型参数,如果不是引用类型,就会无限循环。
2)拷贝构造函数一般都会被隐式地使用,因此不应该是explicit的。
关于拷贝初始化与直接初始化:
直接初始化直接调用与实参匹配的构造函数(包括拷贝构造函数),拷贝初始化首先使用指定构造函数创建一个临时对象,然后使用赋值构造函数将那个临时对象复制到正在创建的对象。
如果使用等号初始化一个变量,实际上执行的是拷贝初始化,如果不使用等号,则执行的是直接初始化。拷贝初始化在下面情况下也发生:
1)将一个对象作为实参传给一个非引用类型的形参。
2)从一个返回类型为非引用类型的函数返回一个对象。
3)用花括号列表初始化一个数组中的元素或一个聚合类中的成员。
(聚合类:所有成员都是public;没有定义构造函数;没有类内初始值;没有基类,没有virtual函数。)
3.赋值构造函数
class Foo{
public:
Foo(){}
Foo & operator=(const Foo&); //赋值构造函数
};
有以下几点说明:
1)为了与内置类型一直(a=b=c),返回一个指向其左侧运算对象的引用。
2)参数是const引用
4.析构函数
5.move构造函数(C++11)
6.move赋值构造函数(C++11)
补充:
1.需要析构函数的类也需要拷贝和赋值操作
class HasPtr{
public:
HasPtr(const string &s = string()) :ps(new string(s)), i(0){}
~HasPtr(){ delete ps; }
private:
string *ps;
int i;
};
例如,上面这个类,需要析构函数来删除ps。如果我们没定义拷贝和赋值操作,默认的拷贝和赋值构造函数会执行下面操作:
#include<iostream>
#include <string>
#include <vector>
using namespace std;
class HasPtr{
public:
HasPtr(const string &s = string()) :ps(new string(s)), i(0){}
~HasPtr(){ delete ps; }/*
HasPtr(const HasPtr &hp){ //默认的拷贝构造函数执行的操作
ps = hp.ps;
i = hp.i;
}*/
private:
string *ps;
int i;
};
int main()
{
HasPtr hp1("test");
HasPtr hp2(hp1); //错误:两个对象的ps指向相同的内存,hp2析构时delete会出错。
return 0;
}
上面出现的问题也就是浅拷贝问题。
浅拷贝:在对象拷贝时,只是对对象中的数据成员进行简单的赋值,如果对象中存在动态成员,即指针,浅拷贝就会让两个指针指向同一个内存,会出现问题。
深拷贝:针对成员变量存在指针的情况,不仅仅是简单的指针赋值,而是重新分配内存空间。
2.需要拷贝操作的类也需要赋值操作,反之亦然
例如,一个类为每个对象分配一个独有的、唯一地序号。
3.使用=default可以显式要求编译器生成默认版本。其中default在类内声明时使用,则隐式地声明为内联函数,如果在类外定义时使用,则不是内联函数。
4.阻止拷贝
1)定义删除的函数
#include<iostream>
#include <string>
#include <vector>
using namespace std;
class NoCopy{
public:
NoCopy() = default;
~NoCopy() = default;
NoCopy(const NoCopy &nc) = delete; //=delete必须在第一次声明时出现
NoCopy& operator=(const NoCopy&nc) = delete;
private:
string *ps;
int i;
};
int main()
{
NoCopy hp1();
NoCopy hp2(hp1); //错误
return 0;
}
注意:对应删除了析构函数的类,编译器不允许定义该类型的变量或创建该类的临时对象,可以动态分配这种类型的对象,但是不能释放这些对象。
#include<iostream>
#include <string>
#include <vector>
using namespace std;
class NoCopy{
public:
NoCopy() = default;
~NoCopy() = delete;
};
int main()
{
NoCopy hp1; //错误
NoCopy *hp2 = new NoCopy(); //正确
delete hp2; //错误
return 0;
}
当不可能拷贝、赋值或销毁类的成员时,累的默认拷贝控制成员就被定义为删除的。
2)private拷贝控制
将拷贝构造函数和拷贝赋值函数声明为private可以阻止拷贝。
补充:取址运算符和取址运算符const
Empty *operator&();
const Empty*operator&() const;