The rule of three, five and zero

Posted on Sun 29 May 2022 in Journal

Rule of Three

C++ 是一门强而又古老的语言,我最早使用的 C++ IDE 是 Borland C++, 一晃二十年过去了。

C++ 的类成员如果是一个对象,或者原始指针类型,或者是资源句柄,它由编译器自动生成的拷贝构造和拷贝赋值通常会有问题,因为它们用的都是浅拷贝,拷贝是指针或句柄的值,而不是拷贝资源。

所以如果一个类对象会被复制及作为容器的元素,一般要求用户自己定义析构函数、拷贝构造函数以及拷贝赋值运算符,除非这个类的成员都是原始的值类型,例如int, char, float等等。

这个规定也称为 “rule of three” -- 三成员函数法则

#include <iostream>
#include <cstddef>
#include <cstring>

class rule_of_three
{
    char* cstring; // raw pointer used as a handle to a dynamically-allocated memory block

    rule_of_three(const char* s, std::size_t n) // to avoid counting twice
    : cstring(new char[n]) // allocate
    {
        std::memcpy(cstring, s, n); // populate
    }
public:
    rule_of_three(const char* s = "")
    : rule_of_three(s, std::strlen(s) + 1) {}

    ~rule_of_three() // I. destructor
    {
        delete[] cstring; // deallocate
    }

    rule_of_three(const rule_of_three& other) // II. copy constructor
    : rule_of_three(other.cstring) {}

    rule_of_three& operator=(const rule_of_three& other) // III. copy assignment
    {
        if (this == &other)
            return *this;

        std::size_t n{std::strlen(other.cstring) + 1};
        char* new_cstring = new char[n];            // allocate
        std::memcpy(new_cstring, other.cstring, n); // populate
        delete[] cstring;                           // deallocate

        cstring = new_cstring;
        return *this;
    }

    operator const char *() const // accessor
    {
        return cstring;
    }
};

int main()
{
    rule_of_three o1{"abc"};
    std::cout << o1 << ' ';
    auto o2{ o1 }; // I. uses copy constructor
    std::cout << o2 << ' ';
    rule_of_three o3("def");
    std::cout << o3 << ' ';
    o3 = o2; // III. uses copy assignment
    std::cout << o3 << ' ';
}   // <- II. all destructors are called 'here'

通过可复制句柄管理不可复制资源的类可能必须将复制赋值和复制构造函数声明为私有,并且不提供它们的定义或将它们定义为已删除。 这是三规则的另一种应用:删除一个并将另一个留给隐式定义很可能会导致错误。

Rule of Five

C++ 11 之后引入了移动语义,由于用户定义的(或 = 默认或 = 声明的)析构函数、复制构造函数或复制赋值运算符的存在阻止了移动构造函数和移动赋值运算符的隐式定义,因此任何需要移动语义的类, 必须声明所有五个特殊成员函数:

这个规定也称为 “rule of three” -- 五成员函数法则

class rule_of_five
{
    char* cstring; // raw pointer used as a handle to a dynamically-allocated memory block
public:
    rule_of_five(const char* s = "") : cstring(nullptr)
    { 
        if (s)
        {
            std::size_t n = std::strlen(s) + 1;
            cstring = new char[n];      // allocate
            std::memcpy(cstring, s, n); // populate 
        } 
    }

    ~rule_of_five()
    {
        delete[] cstring; // deallocate
    }

    rule_of_five(const rule_of_five& other) // copy constructor
    : rule_of_five(other.cstring) {}

    rule_of_five(rule_of_five&& other) noexcept // move constructor
    : cstring(std::exchange(other.cstring, nullptr)) {}

    rule_of_five& operator=(const rule_of_five& other) // copy assignment
    {
        return *this = rule_of_five(other);
    }

    rule_of_five& operator=(rule_of_five&& other) noexcept // move assignment
    {
        std::swap(cstring, other.cstring);
        return *this;
    }

// alternatively, replace both assignment operators with 
//  rule_of_five& operator=(rule_of_five other) noexcept
//  {
//      std::swap(cstring, other.cstring);
//      return *this;
//  }
};

Rule of zero

其实现在 C++ 语言中的智能指针 shared_ptr 的广泛应用,多数情况下我们并不需要拷贝对象,而只需要传递对象的智能指针,所以无需定义这些成员函数。

这个规定也称为 “rule of zero” -- 零成员函数法则

class rule_of_zero
{
    std::string cppstring;
public:
    rule_of_zero(const std::string& arg) : cppstring(arg) {}
};

Some new words

  • tragedy 英 [ˈtrædʒədi] 美 [ˈtrædʒədi]

n. 悲惨事件,惨剧;悲剧(作品);不幸,遗憾;令人非常烦乱(或生气)的情形

  • strike 英 [straɪk] 美 [straɪk]

v. 打,撞;(用手或武器等)打;(使)碰撞;罢工;突然想到,突然意识到;突然袭击;(疾病、灾难等)侵袭;(闪电)击中;引起(强烈感情);(时钟)敲响;达成(协议,妥协),达到(平衡);发现(黄金,矿物,石油);铸造(硬币、奖牌);删除;踢球,击球;攻球得分;使处于特定状态;让(某人)觉得;摆出(姿态);照在……上;把……迷住;弹奏,奏出;划(火柴), 击出(火星);直击(要害),与…...起(原则性)冲突;(在争斗、竞争等中)取得先机;触发(电弧);(电影摄影术)复制;(金融用语)结算,算出;<加>组成(委员会);拆除(帐篷,戏剧布景);降(帆,旗)(以示敬意或投降);把(枝)插进土里生根;(植物,扦插)生根;(小牡蛎)附着;(渔)急拉钓线把鱼钩住;行进;来到;闯出(或开辟)新的(或独立的)事业 n. 罢工,罢市,罢课;(军事)打击;击,踢;(石油等宝贵资源的)发现;(投出的)好球;(棒球中的)击空;(十柱保龄球)一投全倒;不利因素;拒绝,抗拒;(渔)(对已上钩的鱼)急拉钓线;(地层、断层等地质特征的)走向

  • chase 英 [tʃeɪs] 美 [tʃeɪs]

v. 追逐,追赶;匆忙赶往,急奔;追求,努力争取;求爱;驱逐,赶走;撤职;催促;镂刻,雕刻 n. 追赶,追逐;争取,努力获得;狩猎,猎捕;越野赛马,障碍赛马

  • pour 英 [pɔː(r)] 美 [pɔːr]

v. 倾倒,倒出;(给……)斟,倒(饮料等);倾泻,涌流;(人,物)接踵而至,纷至沓来;(雨)倾盆而下;大量注入(资金),捐献(pour sth. into);倾诉,倾吐(pour sth. out);穿上(紧身衣)(pour oneself into) n. 灌,注;倾泻;倾盆大雨

  • generous 英 [ˈdʒenərəs] 美 [ˈdʒenərəs]

adj. 慷慨的,大方的;大量的,丰富的;宽宏大量的

  • blender ['blendə]

n. (美)搅拌机;掺和者;混合物

参考资料

  • https://en.cppreference.com/w/cpp/language/rule_of_three