Rule of Five/Zero in C++

Last Updated: 2023-12-29 03:11:35 Friday

-- TOC --

这是C++这种“事儿多”的编程语言的术语,由于编译器在某些情况下会自动生成对象的一些成员函数,那么在什么情况下自动生成,什么情况下没有。说明这个规则的术语,就是Rule of Five/Zero。

Five指对象的五个成员函数:

  1. destructor;
  2. copy constructor;
  3. copy assignment;
  4. move constructor;
  5. move assignment;

以上五个函数接口,也被称为The Big Five

不熟悉以上成员函数的同学,请先自行学习:

C++的对象Copy语法

C++的对象Move语法

Rule of Five/Zero的内容

rule_of_five_zero.png

The compiler can generate default implementations for each under certain circumstances. Unfortunately, the rules for which methods get generated are complicated and potentially uneven across compilers. You can eliminate this complexity by setting these methods to default/delete or by implementing them as appropriate. This general rule is the rule of five, because there are five methods to specify. Being explicit costs a little time, but it saves a lot of future headaches.

一个对象,如果程序员什么都不提供,以上五个成员函数接口全靠编译器自动生成,这就是Rule of Zero。如果这种情况可以满足需求,请保持!

C++通过管理对象来管理资源。在C语言中,我们对变量的赋值是简单清晰直接的,因为在我们眼中,C语言的变量都是内存块,赋值就是对这个内存块进行填充,有些变量在heap上,有些变量在stack中,有一些全局的放在.data section中。升级到C++后,出现了class object这种复杂变量(变量就是有名称的对象),而那些C语言中的primitive types也全都成为了object。此时,对变量的赋值,自然也就不再是简单的填充内存块了。class object变量的赋值,可能会调用copy constructor/assignment或move constructor/assignment,因为C++依然保持了将内存资源的管理权交给给程序员这种传统美德。编译器提供的default big five,对member variable都是primitive type的class object是很友好的,程序员基本上可以放心。就算class存在复杂class对象member,编译器也能够自动去调用此对象member对应的copy/move constructor/assignment接口。这是C++统一的对象视角!只有在class存在指针member的时候,我们要非常小心,此时编译器提供的default big five基本上是不能用的,需要程序员自己提供。

如上图所示,如果程序员提供了copy constructor,代码使用move语法的时候,会自动调用copy接口。但反过来不会,只定义move,就没有自动的copy。请看下面的测试代码:

#include <cstdio>
#include <cstring>
#include <utility>


struct box {
    char *p;

    box(const char *content) {
        int len = strlen(content);
        if (len != 0) {
            p = new char[len+1]{};
            strcpy(p, content);
        }
    }

    void show() {
        printf("%s\n", p?p:"(null)");
    }

    // copy constructor
    box(const struct box& a) {
        printf("copy constructor\n");
        int len = strlen(a.p);
        if (len != 0) {
            p = new char[len+1]{};
            strcpy(p, a.p);
        }
    }

    ~box() {
        delete[] p;
    }
};


int main(void) {
    struct box a{"abcdefg"};
    struct box b{std::move(a)};
    b.show();
    a.show();
    return 0;
}

上面的测试代码,只定义了copy constructor,但是在main中依然使用move语法,其结果就是编译器调用copy接口来实现move指令。运行结果如下:

$ g++ -Wall -Wextra test_cm.cpp -o test_cm
$ ./test_cm
copy constructor
abcdefg
abcdefg

这个"move”之后,a所拥有的资源还在。

对于big five,建议只要明确写出了其中一个,剩下的全部显式的定义出来,可以是default,也可以用delete表示不支持不允许,也可以根据情况自定义。如下示例,copy都是default,move都是delete,即不允许move操作:

struct xyz {
    xyz(const xyz& z) = default;
    xyz(xyz&& z) = delete;
    xyz& operator=(const xyz& z) = default;
    xyz& operator=(const xyz& z) = delete;
};

本文链接:https://cs.pynote.net/sf/c/cpp/202209012/

-- EOF --

-- MORE --