C++学习笔记之三五法则

简介

三五法则规定了什么时候需要拷贝构造函数、拷贝赋值函数、析构函数等

1. 需要析构函数的类也需要拷贝构造函数和拷贝赋值函数。

通常,若一个类需要析构函数,则代表其合成的析构函数不足以释放类所拥有的资源,其中最典型的就是指针成员(析构时需要手动去释放指针指向的内存)。

所以,若存在自定义(且正确)的析构函数,但使用合成的拷贝构造函数,那么拷贝过去的也只是指针,此时两个对象的指针变量同时指向同一块内存,指向同一块内存的后果很有可能是在两个对象中的析构函数中先后被释放两次。所以需要额外的拷贝控制函数去控制相应资源的拷贝。

所以这类例子的共同点就是:一个对象拥有额外的资源(指针指向的内存),但另一个对象使用合成的拷贝构造函数也同时拥有这块资源。当一方对象被销毁后,析构函数释放了资源,这时另一个对象便失去了这块资源(但程序员还不知道)。

class person
{
public:
    std::string *name;
    int age;

    person(const char* the_name, int the_age)
    {
        name = new std::string(the_name);
        age = the_age;
    }

    ~person()
    {
        delete name;
    }
};

int main(void)
{
    person a("me", 20);
    person b(a);
    std::cout << *b.name << std::endl;

    return 0;
}

在上面的代码中对象b使用合成的拷贝构造函数拷贝对象a的值,这个程序没有什么实际意义。
在main函数返回时,a,b变量会分别被析构,它们的成员name指向同一块内存,所以在程序结束时便会发生错误。

2. 需要拷贝操作的类也需要赋值操作,反之亦然。

需要拷贝操作代表这个类在拷贝时需要进行一些额外的操作。赋值操作=先析构+拷贝,所以拷贝需要的赋值也需要。反之亦然。

3. 析构函数不能是删除的

如果类的析构函数是删除的,那么成员便无法销毁。所以在程序中不能定义这个类的对象。可以动态分配该对象并获得其指针,但无法销毁这个动态分配的对象(delete 失效)。

若上面的类的定义是

class person
{
public:
    std::string *name;
    int age;

    person(const char* the_name, int the_age)
    {
        name = new std::string(the_name);
        age = the_age;
    }
private:
    ~person()
    {
        delete name;
    }
};

则在main函数中定义变量a,b就会发生编译错误,然而,这样的定义却可以通过编译
person *p;
p = new person("me", 20)

但是,这样动态分配的变量是不能被释放的,在调用 delete p 会发生编译错误, 内存泄露就这样发生了。

4. 如果一个类有删除的或不可访问的析构函数,那么其默认和拷贝构造函数会被定义为删除的。

如果没有这条规则,可能会创造出无法被删除的对象。 理论上来说,当析构函数不能被访问时,任何静态定义的对象都不能通过编译器的编译,所以这种情况只会出现在与动态分配有关的拷贝/默认构造函数身上。

5. 如果一个类有const或引用成员,则不能使用合成的拷贝赋值操作。

原因很简单,const或引用成员只能在初始化时被赋值一次,而合成的拷贝赋值操作会对所有成员都进行赋值。显然,它不能赋值const和引用成员,所以合成的拷贝构造函数不能被使用,即会被定义为删除的。

本质上,当不可能拷贝、赋值、或销毁类的所有成员时,类的合成拷贝控制函数就被定义成删除的了。

参考:What is The Rule of Three?

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页