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

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 4128|回复: 1

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

[复制链接]

1109

主题

1649

回帖

7万

积分

用户组: 管理员

一只技术宅

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

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

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

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

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

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

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

规则之“五”:

由于存在用户定义的析构方法(destructor)、复制构造(copy-constructor)、赋值运算符重载(copy-assignment operator)阻止了隐式定义的移动构造(move constructor)和移动赋值运算符(move assignment operator),任何想要设计为允许移动语义的类,都必须声明以下所有五个特殊的成员方法:
  1. class rule_of_five
  2. {
  3.     char* cstring; // 用于管理一个动态分配的内存块的原始指针
  4. public:
  5.     rule_of_five(const char* arg)
  6.     : cstring(new char[std::strlen(arg)+1]) // 分配
  7.     {
  8.         std::strcpy(cstring, arg); // 填充
  9.     }
  10.     ~rule_of_five()
  11.     {
  12.         delete[] cstring;  // 释放
  13.     }
  14.     rule_of_five(const rule_of_five& other) // 复制构造
  15.     {
  16.         cstring = new char[std::strlen(other.cstring) + 1];
  17.         std::strcpy(cstring, other.cstring);
  18.     }
  19.     rule_of_five(rule_of_five&& other) : cstring(other.cstring) // 移动构造
  20.     {
  21.         other.cstring = nullptr;
  22.     }
  23.     rule_of_five& operator=(const rule_of_five& other) // 复制赋值
  24.     {
  25.         char* tmp_cstring = new char[std::strlen(other.cstring) + 1];
  26.         std::strcpy(tmp_cstring, other.cstring);
  27.         delete[] cstring;
  28.         cstring = tmp_cstring;
  29.         return *this;
  30.     }
  31.     rule_of_five& operator=(rule_of_five&& other) // 移动赋值
  32.     {
  33.         if(this!=&other) // 防止自己移动到自己
  34.         {
  35.             delete[] cstring;
  36.             cstring = other.cstring;
  37.             other.cstring = nullptr;
  38.         }
  39.         return *this;
  40.     }
  41. // 或者把两个赋值运算符都换成下面的
  42. //  rule_of_five& operator=(rule_of_five other)
  43. //  {
  44. //      std::swap(cstring, other.cstring);
  45. //      return *this;
  46. //  }
  47. };
复制代码
与规则之“三”不同,不提供移动构造和移动赋值通常不会造成错误,而是错过了优化机会。

规则之“零”:

一些类(下例代码中的std::string)如果它有自己的析构方法,复制/移动构造,或者复制/移动赋值运算符重载,需要有独占性的所有权(遵循“单一责任原则”Single Responsibility Principle)。其它的类(下例代码中的rule_of_zero)不应该有自己的析构方法,复制/移动构造,或者复制/移动赋值运算符重载。
  1. class rule_of_zero
  2. {
  3.     std::string cppstring;
  4. public:
  5.     rule_of_zero(const std::string& arg) : cppstring(arg) {}
  6. };
复制代码
当一个基类被用于多态用途时,它的析构函数大概需要被声明为公开虚函数。这样就会阻止隐式移动(并且废除隐式复制),然后这些特殊的成员方法应该被定义为默认。
  1. class base_of_five_defaults
  2. {
  3. public:
  4.     base_of_five_defaults(const base_of_five_defaults&) = default;
  5.     base_of_five_defaults(base_of_five_defaults&&) = default;
  6.     base_of_five_defaults& operator=(const base_of_five_defaults&) = default;
  7.     base_of_five_defaults& operator=(base_of_five_defaults&&) = default;
  8.     virtual ~base_of_five_defaults() = default;
  9. };
复制代码
然而如果派生类对象不是动态分配的,或者被动态分配在智能指针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
回复

使用道具 举报

51

主题

31

回帖

648

积分

用户组: 大·技术宅

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

使用道具 举报

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-3-28 20:05 , Processed in 0.041261 second(s), 31 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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