用于创建和销毁对象:
默认构造函数
析构函数
用于拷贝和赋值:
拷贝构造函数
赋值运算符(operator=)
用于取地址:
Class* operator&(); // 用于对普通对象取地址
const Class* operator&() const; //用于对const修饰的对象取地址
要了解三五法则先了解下浅拷贝和深拷贝,这里简单举个例子
class A{
public:
A(int t){
a=new int(t);
}
int *a;
};
int main(int argc, char *argv[])
{
A a(11);
A b(a);
std::cout<<*a.a<<std::endl;
std::cout<<*b.a<<std::endl;
printf("address:%p,%p\n",a.a,b.a);
}
输出:
11
11
address:0x1104f70,0x1104f70
上面A类在没有自定义拷贝构造和赋值运算会采用默认的,默认的采用浅拷贝,所以会有上面输出的现象,ab两个对象中成员a的地址是一个因此值肯定也是一个。上面并未自定义析构函数因此存在内存泄漏,那么我们加上析构函数看看
class A{
public:
A(int t){
a=new int(t);
}
~A(){
if(a!=nullptr)
delete a;
}
int *a;
};
int main(int argc, char *argv[])
{
A a(11);
A b(a);
std::cout<<*a.a<<std::endl;
std::cout<<*b.a<<std::endl;
printf("address:%p,%p\n",a.a,b.a);
}
输出:
11
11
free(): double free detected in tcache 2
通过输出仔细考虑下不难发现ab两个对象的成员所指向的是同一个地址,在析构的时候double free了,这就会导致程序崩溃。
class A{
public:
A(int t){
a=new int(t);
}
~A(){
if(a!=nullptr)
delete a;
}
A(const A &a){
this->a = new int(*a.a);
}
int *a;
};
int main(int argc, char *argv[])
{
A a(11);
A b(a);
std::cout<<*a.a<<std::endl;
std::cout<<*b.a<<std::endl;
printf("address:%p,%p\n",a.a,b.a);
}
输出:
11
11
address:0x9e8f70,0x9e8f90
上面代码自定义了拷贝构造,实现了深拷贝,可以看出b对象只是将a对象内成员a指针存储的值拷贝了过来,b对象内a成员的地址是独立的。其实深拷贝和浅拷贝的本质就是对对象中指针成员的处理不同。浅拷贝只是单纯的将对象中的成员赋值指针的赋值其实是地址的拷贝,深拷贝会将指针从新new一个空间然后把原来成员内存中存放的值放到new的这部分空间中实现值的传递。
“三”是指拷贝构造、赋值运算、析构这三个函数,“五”是在前面三个的基础之上再加上移动构造、移动赋值运算函数。三五法则就是说明这些函数之间的关系。下面就通过法则内容了解下它们之间的关系。
拷贝构造和赋值运算一定要保持同步。
为了减少拷贝带来的额外开销,移动构造函数和移动赋值运算符能不开辟新内存完成对对象的移动,减少开销,所以定义它们能够减少性能上的开销。