技术宅的结界

 找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 293|回复: 1
收起左侧

【C++】面向对象潜规则之:构造析构赋值——三五零(The rule of three/five/zero)

[复制链接]

995

主题

2207

帖子

5万

积分

用户组: 管理员

一只技术宅

UID
1
精华
197
威望
261 点
宅币
16463 个
贡献
32446 次
宅之契约
0 份
在线时间
1565 小时
注册时间
2014-1-26
发表于 2018-2-17 04:24:34 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有帐号?立即注册→加入我们

x
规则之“三”:如果你要自己编写类的构造方法的话,你就很有可能逃不掉不得不编写类的析构方法和赋值方法的命运。

如果你写了个构造函数,然后你在构造函数里面malloc了,或者new了,那么你就得在析构函数里free,或者delete。也就是说,这些配套的函数和你的类的构造和析构函数也是配套的。

而且如果你把你的类赋值给别的类,但是你没有实现赋值方法的话,虽然编译器会帮你复制值,但它复制的只是“表面上”的值。也就是说,它其实是把你new出来的、或者是malloc出来的指针,直接赋值给别的类,而不是赋值指针指向的内存(当然你还得先分配内存才行,但它没有。)

此事要是发生了内存泄漏的话,那也只能怪自己咯。不掌握底层原理的话,就很容易犯这样的错误。
[C++] 纯文本查看 复制代码
class rule_of_three
{
    char* cstring; // 一个“原始指针”,用来处理动态分配的内存。
 public:
    rule_of_three(const char* arg)
    : cstring(new char[std::strlen(arg)+1]) // 分配内存
    {
        std::strcpy(cstring, arg); // 复制值
    }
    ~rule_of_three()
    {
        delete[] cstring;  // 释放内存
    }
    rule_of_three(const rule_of_three& other) // 构造赋值
    {
        cstring = new char[std::strlen(other.cstring) + 1];
        std::strcpy(cstring, other.cstring);
    }
    rule_of_three& operator=(const rule_of_three& other) // 赋值运算符重载
    {
        char* tmp_cstring = new char[std::strlen(other.cstring) + 1];
        std::strcpy(tmp_cstring, other.cstring);
        delete[] cstring;
        cstring = tmp_cstring;
        return *this;
    }
// 或者另外的例子,重用析构和复制器
//  rule_of_three& operator=(rule_of_three other)
//  {
//      std::swap(cstring, other.cstring);
//      return *this;
//  }
};
如果一个类它通过可复制的句柄(之类的玩意儿等)来管理一些无法复制的资源的话,你就得把它的赋值运算符重载(copy-assignment operator)和复制构造方法(copy-constructor)声明为私有,并且不提供它们的定义,或者把它们定义为已删除。

规则之“五”:

由于存在用户定义的析构方法(destructor)、复制构造(copy-constructor)、赋值运算符重载(copy-assignment operator)阻止了隐式定义的移动构造(move constructor)和移动赋值运算符(move assignment operator),任何想要设计为允许移动语义的类,都必须声明以下所有五个特殊的成员方法:
[C++] 纯文本查看 复制代码
class rule_of_five
{
    char* cstring; // 用于管理一个动态分配的内存块的原始指针
 public:
    rule_of_five(const char* arg)
    : cstring(new char[std::strlen(arg)+1]) // 分配
    {
        std::strcpy(cstring, arg); // 填充
    }
    ~rule_of_five()
    {
        delete[] cstring;  // 释放
    }
    rule_of_five(const rule_of_five& other) // 复制构造
    {
        cstring = new char[std::strlen(other.cstring) + 1];
        std::strcpy(cstring, other.cstring);
    }
    rule_of_five(rule_of_five&& other) : cstring(other.cstring) // 移动构造
    {
        other.cstring = nullptr;
    }
    rule_of_five& operator=(const rule_of_five& other) // 复制赋值
    {
        char* tmp_cstring = new char[std::strlen(other.cstring) + 1];
        std::strcpy(tmp_cstring, other.cstring);
        delete[] cstring;
        cstring = tmp_cstring;
        return *this;
    }
    rule_of_five& operator=(rule_of_five&& other) // 移动赋值
    {
        if(this!=&other) // 防止自己移动到自己
        {
            delete[] cstring;
            cstring = other.cstring;
            other.cstring = nullptr;
        }
        return *this;
    }
// 或者把两个赋值运算符都换成下面的
//  rule_of_five& operator=(rule_of_five other)
//  {
//      std::swap(cstring, other.cstring);
//      return *this;
//  }
};
与规则之“三”不同,不提供移动构造和移动赋值通常不会造成错误,而是错过了优化机会。

规则之“零”:

一些类(下例代码中的std::string)如果它有自己的析构方法,复制/移动构造,或者复制/移动赋值运算符重载,需要有独占性的所有权(遵循“单一责任原则”Single Responsibility Principle)。其它的类(下例代码中的rule_of_zero)不应该有自己的析构方法,复制/移动构造,或者复制/移动赋值运算符重载。
[C++] 纯文本查看 复制代码
class rule_of_zero
{
    std::string cppstring;
 public:
    rule_of_zero(const std::string& arg) : cppstring(arg) {}
};
当一个基类被用于多态用途时,它的析构函数大概需要被声明为公开虚函数。这样就会阻止隐式移动(并且废除隐式复制),然后这些特殊的成员方法应该被定义为默认。
[C++] 纯文本查看 复制代码
class base_of_five_defaults
{
 public:
    base_of_five_defaults(const base_of_five_defaults&) = default;
    base_of_five_defaults(base_of_five_defaults&&) = default;
    base_of_five_defaults& operator=(const base_of_five_defaults&) = default;
    base_of_five_defaults& operator=(base_of_five_defaults&&) = default;
    virtual ~base_of_five_defaults() = default;
};
然而如果派生类对象不是动态分配的,或者被动态分配在智能指针std::shared_ptr(比如用std::make_shared),你就可以不用这么干。智能指针甚至可以在把派生类转换为std::shared_ptr<Base>后依然能调用派生类析构函数。

参考资料:
规则之三五零
http://en.cppreference.com/w/cpp/language/rule_of_three

规则之零
https://blog.rmf.io/cxx11/rule-of-zero

关注规则之零
http://scottmeyers.blogspot.jp/2014/03/a-concern-about-rule-of-zero.html

47

主题

67

帖子

590

积分

用户组: 大·技术宅

UID
3260
精华
7
威望
12 点
宅币
463 个
贡献
1 次
宅之契约
0 份
在线时间
19 小时
注册时间
2017-12-26
发表于 2018-2-17 09:49:47 | 显示全部楼层
不错
回复

使用道具 举报

本版积分规则

QQ|申请友链|Archiver|手机版|小黑屋|技术宅的结界 ( 滇ICP备16008837号|网站地图

GMT+8, 2018-9-24 08:14 , Processed in 0.104112 second(s), 37 queries , Gzip On.

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表