1.合成拷贝成员
在我们的自定义的类中,如果我们没有自定义拷贝构造函数、拷贝赋值函数、以及析构函数,编译器将会为我们自动合成上述函数。
class copy_test
{
public:
void set(int val)
{
test = val;
}
void show()
{
cout << test << endl;
}
private:
int test;
};
int main()
{
copy_test c1;
c1.set(1);//c1的test设置为1
copy_test c2(c1);//调用合成的拷贝构造函数
copy_test c3 = c1;//调用合成的拷贝赋值函数
c1.show();
c2.show();
c3.show();
}
上述输出结果均为1,说明编译器去确实为我们生成了合成的拷贝构造和拷贝赋值运算符。但是对于编译器自己合成的拷贝构造函数和拷贝赋值运算符,编译器仅仅是采用简单的赋值方式拷贝,如上的例子的合成拷贝构造函数可能是这样的:
copy_test::copy_test(const copy_test &c1)
{
test=c1.test;
}
2.自定义析构函数
析构函数会在对象即将被销毁的时候进行调用,自定义的析构函数一般用于将函数函数内置的动态内存空间进行释放。
但是对于编译器合成的拷贝控制成员,可能会存在一些问题,如果类中的某个成员为指向动态数据类型的指针或者是一个引用时,当进行拷贝时,仅仅是通过指针的简单赋值,那么就会出现多个对象的内置指针指向同一片内存区域。当调用自定义的析构函数进行内存释放时可能会出现执行同一内存的指针被多次释放,这显然是不合法的。
所以当一个类使用的是自定义的析构函数时,毫无疑问这个类应该使用自定义的拷贝构造函数和拷贝赋值运算符。
上述案例可以按照如下所示改编:
class copy_test
{
public:
copy_test(int *p)
{
test = p;
}
copy_test(const copy_test &c)
{
test = new int(*(c.test));
}
copy_test& operator=(const copy_test &c)
{
test = new int(*(c.test));
return *this;
}
void show()
{
cout << *test << endl;
}
~copy_test()
{
delete test;
}
private:
int *test;
};
int main()
{
copy_test c1(new int(10));
copy_test c2(c1);
copy_test c3 = c1;
c1.show();
c2.show();
c3.show();
}
一个类的析构函数和赋值构造函数往往是成对出现的。
3.=default和=delete
如果我们希望编译器为我们显示的生成合成拷贝控制成员,我们可以使用 =default 来进行显示说明。
我们只能对具有合成版本的成员函数使用=default即默认构造函数、拷贝构造函数、赋值运算符重载、析构函数。
有时候我们希望能够阻止类的拷贝和赋值,如我们的IO类不允许赋值和拷贝,我们通过使用=delete来防止函数进行拷贝和赋值。
lass test
{
public:
test() = default;//使用默认构造函数
test(const test& t) = delete;//不使用拷贝构造函数
test& operator = (const test &t) = delete;//不使用合成赋值运算符
~test() = default;//使用合成的析构函数
};
=delete的使用应该具有以下规则:如果有一个类的数据成员不能默认构造、拷贝、复制或销毁,则相应的成员函数将会被定义为删除的。
- 析构函数不能是删除的成员,否则对象将无法销毁。
- 一个成员有删除的或者不可访问的析构函数则会导致合成的默认和拷贝构造函数被定义为删除的。
- 具有引用或者const成员的类具有删除的合成拷贝构造和合成复制函数,对一个const的成员复制是不允许的,对引用的复制,依然是同一个变量的引用,这没有太大意义。