[CPP] Big Three
前言
上一篇攻略中,我们已经充分理解了不带指针的类的设计原则,并且还从标准库设计大师的作品里收获了不少功力。而这一篇攻略,将继续完成基于对象的类的关卡,解决这一关的最后一个问题,那就是带指针的类。在这途中,我们将会遇到知名的 BIG THREE ,剖析 BIG THREE 的设计原则!
带指针的类
一个带指针的类,我们可以想到很多,但是 String 类一定是一个最经典的例子。在这里,我们自己设计一个 String 类,看看带指针的类在设计过程中到底需要注意什么情况。source code
class String
{
public:
String(const char *cstr = 0);
String(const String &str);
String& operator = (const String &str);
~String();
char *get_c_str() const { return m_data; }
private:
char *m_data;
};
一些人可能会问,这个类的实现也可以不使用指针,直接声明一个数组来存储字符不就行了么?使用数组当然可以,不过这样的设计很low,比较明显的一个理由就是我们在一开始并不知道字符串的长度多长,太少则浪费空间,太长则导致溢出。在这种情况下,使用指针是非常好而且非常普遍的,在32位的操作系统中,一个指针的大小是4个字节,因此一个 String 对象的大小十分小,对于效率的提升非常大。
BIG THREE
我们常说的 BIG THREE 就是如下三个:
String(const String &str);
String& operator = (const String &str);
~String();
分别是拷贝构造函数、拷贝赋值函数和析构函数。那么问题来了,在上一篇攻略中,我们的 complex 类为什么没有 BIG THREE 呢?其实也有的,只是我们没有显式地写出来,而是使用的编译器提供的 BIG THREE。以下我们逐一分析为什么要使用它们,为什么不能使用默认的 BIG THREE。
接下来我们考虑使用者会怎么使用我们的 String 类。首先能够想到的就是构造函数,这部分同上一篇攻略类似,考虑下参数、默认参数以及初始化列表的问题:
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';
}
}
对于一个带指针的类,最为麻烦,而且也是最需要关注的点就是如何进行赋值,赋值的方法有如下几种:
String s1("hello");
String s2(s1);
String s3 = s1;
s3 = s1;
对于
String s2(s1);
s2是第一次出现,因此会调用构造函数,而参数是 String 对象,因此会调用 BIG THREE 的第一个函数,拷贝构造函数。
String::String(const String &str)
{
m_data = new char[strlen(str) + 1];
strcpy(m_data, str.m_data);
}
拷贝构造函数也是构造函数,它的特征非常明显,就是参数const String &str
为同类对象,这个函数比较简单,我们就不多嘴了。一些朋友可能会对下面这个等式产生疑问,
String s3 = s1;
其实这个用法和String s3(s1);
完全相同,同样也是调用拷贝构造函数。那么我们为什么需要手写,而不能使用默认的拷贝构造函数呢?这是由指针导致的。一个默认的拷贝构造函数大概长这样:
String::String(const String &str)
{
m_data = str.m_data;
}
这样的构造函数会造成大麻烦,这涉及到深浅拷贝的问题。默认的拷贝函数是浅拷贝:
s1和s2都指向同一份字符串,当这份字符串被释放了,就会导致野指针问题,但是这并不是我们想要的,我们需要的是深拷贝:
接下来是
s3 = s1;
这种情况的赋值是操作在两个已存在的对象上,这就不关构造函数任何事了。我们同样需要s3和s1指向两份不同的字符串,而默认的拷贝赋值函数会是这样的:
String& String::operator=(const String& str)
{
this.m_data = str.m_data;
return *this;
}
这样造成的后果就是,两个对象指向的还是同一份字符串,这是很不好的,因此我们必须手写拷贝赋值函数,也就是操作符重载函数:
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;
}
我们的函数首先判断是不是自我赋值s1 = s1;
,如果是自我赋值,那么直接返回自己。一般情况下,我们必须首先杀死自己,然后再重新分配一片内存,再将字符填进去。这样就完成了我们的赋值函数。
最后是我们的析构函数函数,同样地,我们不使用默认的析构函数,而是自己写一个析构函数,那是因为默认的析构函数只会回首指针占用的4个字节的空间,而指针指向的空间却没有回收,导致内存泄漏:
因此我们必须要手动回收字符串占有的空间,写出我们的析构函数:
String::~String()
{
delete[] m_data;
}
总结
这一篇攻略我们讨论了拷贝构造函数、拷贝赋值函数和析构函数,以及默认的 BIG THREE 。在带指针的类中,我们必须要手写 BIG THREE ,这样做的原因在于编译器默认给我们的拷贝赋值的方法是浅拷贝的方法,而我们需要深拷贝。在对象结束生命时,我们需要手动回收分配的空间,以免造成内存泄漏。
这一篇攻略就到此为止,接下来我们将深度分析分配空间的种种问题,包括 new delete 操作以及内存空间管理的浅层问题。
Reference
C++面向对象高级编程, 侯捷.
[CPP] Big Three的更多相关文章
- 使用“Cocos引擎”创建的cpp工程如何在VS中调试Cocos2d-x源码
前段时间Cocos2d-x更新了一个Cocos引擎,这是一个集合源码,IDE,Studio这一家老小的整合包,我们可以使用这个Cocos引擎来创建我们的项目. 在Cocos2d-x被整合到Cocos引 ...
- Json CPP 中文支持与入门示例
在每一个Json Cpp自带*.cpp文件头加上: #include "stdafx.h" 将Json Cpp对自带的头文件的引用修改为单引号方式,例如json_reader.cp ...
- cpp 调用python
在用cpp调用python时, 出现致命错误: no module named site , 原因解释器在搜索路径下没有找到python库.可以在调用Py_Initialize前,调用 Py_Se ...
- nginx+fastcgi+c/cpp
参考:http://github.tiankonguse.com/blog/2015/01/19/cgi-nginx-three/ 跟着做了一遍,然后根据记忆写的,不清楚有没错漏步骤,希望多多评论多多 ...
- APM程序分析-ArduCopter.cpp
该文件是APM的主文件. #define SCHED_TASK(func, rate_hz, max_time_micros) SCHED_TASK_CLASS(Copter, &copter ...
- APM程序分析-AC_WPNav.cpp
APM程序分析 主程序在ArduCopter.cpp的loop()函数. /// advance_wp_target_along_track - move target location along ...
- Dev Cpp 输出中文字符问题
最近 c++ 上机作业,vc++6.0 挂了没法用,只好用 Dev Cpp 先顶替一下,然而在遇到输出中文字符的时候出现了乱码的情况,但这种情况又非常诡异.于是简单了解了一下写成此博客. [写在前面] ...
- 【安卓】aidl.exe E 10744 10584 io_delegate.cpp:102] Error while creating directories: Invalid argument
这几天在使用.aidl文件的时候eclipse的控制台总是爆出如下提示: aidl.exe E 10744 10584 io_delegate.cpp:102] Error while creatin ...
- Identify Memory Leaks in Visual CPP Applications —— VLD内存泄漏检测工具
原文地址:http://www.codeproject.com/Articles/1045847/Identify-Memory-Leaks-in-Visual-CPP-Applications 基于 ...
- 估计PI——OpenCV&Cpp
来源:<Learning Image Processing With OpenCV> 算法原理:蒙特卡洛 PI的计算公式: Cpp代码: #include <opencv2/open ...
随机推荐
- 扫描指定ip的端口(C#)
class PingExam { public static void Main() { Ping ping = new Ping(); string ip = "192.168.1.43& ...
- 关于easyui展示慢的Debug
同事开发的软件系统采用Easyui做的前台界面,当业务变得比较复杂之后,展示效果就变得很慢,于是我开始了原因的排查,现在已经找到了具体的原因,所以拿出来与大家一起分享调试过程. 既然调试的是前端,那么 ...
- 关于类属性值校验的一点记录 【知识点Attribute】
好久没有进来了,之前励志坚持写博客,记录自己在做代码搬运工这段历程中点滴,可是仅仅只坚持了几天,就放弃了!果然是,世上无难事,只要肯放弃!哈哈……闲话不多说,开始进入正题,给自己留点笔记,避免将来老了 ...
- netcore中使用bower还原出错的解决方法
近期BitAdminCore框架在创建时,还原bower包出现502错 打开地址,发现原为是因为bower服务调整导致的. 果断处理: 1.通过管理员模式,启动命令行 2.进入npm所在目录 3.执行 ...
- Core引用Jquery文件
静态文件放在wwwroot里边 不然浏览器会报错文件不存在.
- CCF CSP 201803-1 跳一跳
题目链接:http://118.190.20.162/view.page?gpid=T73 问题描述 近来,跳一跳这款小游戏风靡全国,受到不少玩家的喜爱. 简化后的跳一跳规则如下:玩家每次从当前方块跳 ...
- “全栈2019”Java多线程第十二章:后台线程setDaemon()方法详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- 给洛谷填坑的spj……
这里提供了洛谷某些题的$special\ judge$,供需要的oier拿过去对拍. 1.P3825 #include "testlib.h" using namespace st ...
- ie 8及以下 前端cors ajax跨域须知
http://www.cnblogs.com/xishuai/p/jquery-ajax-ie8-cors.html
- P4090 [USACO17DEC]Greedy Gift Takers
题目链接 题意分析 首先 如果当前序列中一头奶牛拿不到礼物的话 那么他后面的奶牛也拿不到礼物 所以我们可以二分 由于可以操作无限次 所以我们对于当前\([1,mid)\)的奶牛按照\(c\)值排序之后 ...