- UID
- 1
- 精华
- 积分
- 76361
- 威望
- 点
- 宅币
- 个
- 贡献
- 次
- 宅之契约
- 份
- 最后登录
- 1970-1-1
- 在线时间
- 小时
|
规则之“三”:如果你要自己编写类的构造方法的话,你就很有可能逃不掉不得不编写类的析构方法和赋值方法的命运。
如果你写了个构造函数,然后你在构造函数里面malloc了,或者new了,那么你就得在析构函数里free,或者delete。也就是说,这些配套的函数和你的类的构造和析构函数也是配套的。
而且如果你把你的类赋值给别的类,但是你没有实现赋值方法的话,虽然编译器会帮你复制值,但它复制的只是“表面上”的值。也就是说,它其实是把你new出来的、或者是malloc出来的指针,直接赋值给别的类,而不是赋值指针指向的内存(当然你还得先分配内存才行,但它没有。)
此事要是发生了内存泄漏的话,那也只能怪自己咯。不掌握底层原理的话,就很容易犯这样的错误。- 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),任何想要设计为允许移动语义的类,都必须声明以下所有五个特殊的成员方法:- 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)不应该有自己的析构方法,复制/移动构造,或者复制/移动赋值运算符重载。- class rule_of_zero
- {
- std::string cppstring;
- public:
- rule_of_zero(const std::string& arg) : cppstring(arg) {}
- };
复制代码 当一个基类被用于多态用途时,它的析构函数大概需要被声明为公开虚函数。这样就会阻止隐式移动(并且废除隐式复制),然后这些特殊的成员方法应该被定义为默认。- 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 |
|