目录
[toc]
概述
“三”是指拷贝构造函数、拷贝赋值运算符、析构函数这三者之间关系,“五”是在前面三个的基础之上再加上移动构造函数、移动赋值运算符这两个
三五法则
需要自定义析构函数的类也需要自定义拷贝和赋值操作
- 当类含有指针成员,构造函数new了一块动态内存并让这个指针成员指向它,这个类析构时就需要执行delete去释放指针指向的内存,但是合成析构函数不会delete这个指针,所以这种类需要自定义析构函数
- 同时,这种类在发生拷贝或者赋值时,如果执行合成拷贝构造函数和合成拷贝赋值运算符,只会进行简单的浅拷贝,会使得多个类对象的指针成员指向同一块内存;这样的话,当某个类对象结束了它的生命周期,会执行它自定义的析构函数,delete它的指针成员,于是这块内存便被释放了,那么此时其他的类对象的指针成员将成为空悬指针,或者多个类对象同时被销毁时,这块内存会被delete多次,这都是会发生错误的
- 浅拷贝:在进行类对象拷贝时,如果类对象含有指针成员,浅拷贝是仅仅拷贝了一个指针,让这个指针也指向原来已经存在的内存
- 深拷贝:深拷贝是生成一个指针,并新开辟了一块内存,把原来那块内存上的内容拷贝过去,并让新生成的指针指向这块新内存
- 默认拷贝构造函数,进行的是浅拷贝,会发生上述所说的错误,所以要自定义拷贝构造函数和拷贝赋值运算符,进行深拷贝
- 基类的析构函数可以不遵循此法则:一个基类总是需要虚折构函数来防止内存泄露
- 首先要明确的是,每个析构函数只会清理自己所在层级创造的成员
- 当delete一个指向派生类对象的基类指针时,如果基类的析构函数没有定义成虚函数,则编译器实现静态绑定,在delete这个基类的指针时,只会执行基类的析构函数,释放基类成员,派生类成员得不到释放,此时会导致释放内存不完全,导致内存泄露
class HasPtr {
public:
HasPtr(const string& s = string()) : i(0) {
ps = new string(s);
}
//自定义拷贝构造函数
HasPtr(const HasPtr& hp) : i(hp.i) {
ps = new string(*hp.ps);
}
//自定义拷贝赋值运算符
HasPtr& operator = (const HasPtr& rhs_hp) {
if (this != &rhs_hp) {
string *temp_ps = new string(*rhs_hp.ps);//new在delete前,为了保证安全性,如果new不出来,那么异常就在这发生了,下面那行delete就不不会发生了
delete ps;
ps = temp_ps;
i = rhs_hp.i;
}//temp_ps离开了它的作用域,指针本身被销毁,但是它指向的动态内存不会被销毁,因为没有执行delete
return *this;
}
//自定义折构函数
~HasPtr() {
delete ps;
}
private:
string *ps;
int i;
};
需要自定义拷贝操作的类也需要自定义赋值操作,反之亦然
- 一个类有一个数据成员代表编号,当需要这个类的每一个类对象都有一个唯一的编号,此时如果仅仅使用合成拷贝构造函数的话,发生拷贝时,拷贝出来的对象的编号肯定就一样了,这个时候就需要自定义拷贝构造函数
- 这种情况下需要自定义拷贝构造函数,那肯定也要自定义拷贝赋值运算符,反过来也是一样的道理
析构函数不能是删除的
如果类的析构函数是删除的,那么成员便无法销毁。所以在程序中不能定义这个类的对象。可以动态分配该对象并获得其指针,但无法销毁这个动态分配的对象(delete 失效)。
编译器合成的三五法则相关函数可能是删除的:
- 一个类下的类成员析构函数是删除的,那么该类的析构函数也影响为删除的;
- 一个类下的类成员析构函数是删除的,那么该类的默认构造函数也是删除的;
- 一个类下的类成员拷贝构造函数是删除的,那么该类下的拷贝构造函数也是删除的;
- 一个类下的类成员拷贝赋值运算符是删除的,那么该类下的拷贝赋值运算符函数也是删除的;
- 一个类下,引用没有初始化器(默认初值),该类的默认构造函数是删除的;
- 一个类下,const 对象没有初始化器(默认初值),该类的默认构造函数是删除的;
如果一个类成员有删除的或不可访问的析构函数,那么其默认构造函数和拷贝构造函数会被定义为删除的
如果没有这条规则,可能会创造出无法被删除的对象。 理论上来说,当析构函数不能被访问时,任何静态定义的对象都不能通过编译器的编译,所以这种情况只会出现在与动态分配有关的拷贝/默认构造函数身上。
如果一个类有const或引用成员,则不能使用合成的拷贝赋值操作。(无法默认构造的const成员的类 则该类就无默认构造函数)
- const或引用成员只能在初始化时被赋值一次,而默认拷贝赋值操作会对所有成员都进行赋值。显然,它不能赋值const和引用成员,所以合成的拷贝构造函数不能被使用,即会被定义为删除的。
留言