c++ 之 “Big Three”
最新版本:https://blog.csdn.net/qq_62357480/article/details/129151139?spm=1001.2014.3001.5501
简介
什么是Big Three?
三法则(英语:rule of three,the Law of The Big Three,The Big Three;三法则,三大定律)在 C++ 程序设计里,它是一个以设计的基本原则而制定的定律,三法则的要求在于,假如类有明显地定义下列其中一个成员函数,那么程序员必须连其他二个成员函数也一同编写至类内,亦即下列三个成员函数缺一不可:
析构函数(Destructor)
复制构造函数(copy constructor)
复制赋值运算符(copy assignment operator)
上述三个函数是特别的成员函数,假如程序员没有自行定义或是编写声明它们,那么编译器会自动地创建它们,并且会编译至应用程序内。相反地,假如程序员有定义上述三者其中一个函数,那么由编译器自动产生出来的上述三个函数是不会搭配到这个类内。三法则(Rule of three)这个专有名词是由 Marshall Cline 于 1991 年创立的。
简单举例
那么为什么要这么做呢?当数据成员有管理动态开辟内存的指针时,需要使用该原则。取其一来说:
当一个class具有一个指针类型成员时,如下:
class String{
private char* str;
}
当他进行以下几个操作的时候:
String s = "hello,world!";
String s1(s);
String s2 = s;
第一个方法就是一个显示的调用拷贝构造函数,我们可以很简单的写出对应的(当然这也是必要的):
String::String(const char * cstr)
{
// code
}
但是当涉及到后面两个的做法时,第二个是一个拷贝构造的方式创建对象,第三个是一个拷贝赋值的方式创建对象。如果我们自己没有定义相对应自己的class的这两个函数时,编译器会给我们自动生成一个操作该步骤的函数,这是一个逐个字节拷贝的过程,将s中的每一个字节逐个复制给s1和s2。这样的做法有一个坏处就是,当class中存在指针类型或者需要动态分配内存的成员变量时,很大可能上会出现悬垂指针。
什么是悬垂指针,假设如下:
String s = "hello,world!";
String s1(s);
s 和 s1都属于一个char类型的指针,当你没有给出对应的拷贝构造和拷贝赋值函数时,进行如上操作时,对s进行每一个字节的复制,那么传给s1和s2的就都是指向 “hello,world” 的指针,假设s在离开作用域后被自动回收delete,但是s1因为其他操作生命周期并没有结束,那么s在delete时已经将 “hello,world” 的内存释放了,那么现在的s1指向已经消失了,变成了一个悬垂指针。(野指针和悬垂指针的区别就是,悬垂指针是原先存在指向内存但是指向内存被释放了,而野指针一直没有指向的内存,未经初始化的指针。)
悬垂指针和野指针的存在都是非常危险的,很可能就会引起程序的崩溃。
简单应用
根据侯捷老师的课程,列举出一个较为简单的Big There函数
- class定义
//三个特殊函数:拷贝构造函数 拷贝赋值函数 析构函数
//big three : 这是三个极其重要的函数
#include<iostream>
#include <cstring>
class String
{
private:
char * m_data;
public:
String (const char * cstr = 0);
String (const String & str); //接受一个像自己一样的对象称为拷贝构造,如果不给出,编译器会自动添加一个。 -- 拷贝构造函数
String & operator = (const String & str); //拷贝赋值在 = 操作符的重载中实现 -- 拷贝赋值函数
~String (); //析构函数
char* get_c_str() const { return m_data; }; //返回字符串指针的成员函数
};
- 构造函数
// 构造函数
inline
String::String(const char * cstr)
{
if (cstr) { //如果传进来的是一个字符串,那么就根据字符串新分配空间。
m_data = new char(strlen(cstr) + 1);
strcpy( m_data, cstr);
}
else {
m_data = new char(1);
*m_data = '\0'; //已结束符号为终止符
}
}
- 析构函数
// destructor
inline
String::~String()
{
delete[] m_data; //这是属于动态分配的内存
}
- 拷贝构造函数
// 拷贝构造函数
inline
String::String (const String& str)
{
m_data = new char[ strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
}
- 拷贝赋值函数
// 拷贝赋值函数
inline
String& String::operator=(const String& str) {
if (this == &str)
return *this; // 检测是否是自我赋值
delete[] m_data; // 清理之前的内存
m_data = new char[strlen(str.m_data) + 1]; // 重新分配内存 (如果没有判断是自我赋值,那这一步将会报错)
strcpy(m_data, str.m_data);
return *this;
}
如有错误欢迎指正以及评论区交流!