C++由自定义类默认生成函数到三五法则

发布时间:2024年01月15日

C++创建一个对象默认生成的函数

用于创建和销毁对象:
默认构造函数
析构函数
用于拷贝和赋值:
拷贝构造函数
赋值运算符(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的这部分空间中实现值的传递。

什么是三五法则?

“三”是指拷贝构造、赋值运算、析构这三个函数,“五”是在前面三个的基础之上再加上移动构造、移动赋值运算函数。三五法则就是说明这些函数之间的关系。下面就通过法则内容了解下它们之间的关系。

法则一:需要自定义析构函数的类也需要自定义拷贝和赋值操作

  1. 当自定义的类中存在指针成员时,有时候会在构造函数中去给这个指针new一块空间,然后在析构时候将这个指针释放掉。这时候就需要我们去自定义一个析构函数。
  2. 如果满足1这种情况我们也应该考虑自定义拷贝构造和赋值运算函数,因为如果使用默认的拷贝构造和赋值运算函数采用的时浅拷贝,采用浅拷贝就有可能发生double free导致程序崩溃。所以我们要自定义拷贝构造和赋值运算进行深拷贝。
  3. 基类的析构函数可以不遵循此法则因为一个基类需要虚折构函数来防止内存泄露。对于虚函数,虚析构函数不懂得可以看https://blog.csdn.net/qq_33865609/article/details/117515859

法则二:需要定义拷贝构造函数的类也需要定义赋值运算函数,反之亦然

拷贝构造和赋值运算一定要保持同步。

法则三:一般来说,如果一个类定义了任何一个拷贝控制成员,它就应该定义所有五个拷贝控制成员

为了减少拷贝带来的额外开销,移动构造函数和移动赋值运算符能不开辟新内存完成对对象的移动,减少开销,所以定义它们能够减少性能上的开销。

文章来源:https://blog.csdn.net/qq_33865609/article/details/135603300
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。