String 类的实现(1)浅拷贝存在的问题以及深拷贝实现
1. 浅拷贝 : 也称位拷贝 , 编译器只是直接将指针的值拷贝过来, 结果多个对象共用 同 一块内存, 当一个对象将这块内 存释放掉之后, 另 一些对象不知道该块空间已经还给了系统, 以为还有效, 所以在对这段内存进行操作的时候, 发生了违规访问。
先上代码
class String
{
public:
/* 浅拷贝---下列代码相当于系统合成的
String()
{
_pStr = new char;
*_pStr = '\0';
}*/
String(const char *pStr = "")
{
if (pStr == NULL)
{
_pStr = new char[];
*_pStr = '\0';
}
else
{
_pStr = new char[strlen(pStr) + ];
strcpy(_pStr, pStr);
}
}
String(const String& s)
:_pStr(s._pStr)
{}
~String()
{
if (_pStr)
delete[] _pStr;
_pStr = NULL;
}
String& operator=(const String& s)
{
if (this != &s)
{
_pStr = s._pStr;
}
return *this;
}
private:
char *_pStr;
};/*存在问题:1.同一对象析构多次,程序崩溃;2.内存泄漏*/
int main()
{
String s1;
String s2 = "hello word";
String s3(s2);
s1[0] = '5';
String s4;
s4 = s2;
}
编译时可以轻松通过,但是这段代码是有问题的,运行时程序崩溃

这是String类的一个经典反例,下面来具体分析一下这段代码存在的问题:

当类里面有指针对象时, 拷贝构造和赋值运算符重载只进行值拷贝(浅拷贝) , 两个对象指向同一块内 存, 对象销毁时该空间被释放了 两次, 因此程序崩溃!下面的 s1[0] = '5'; String s4; s3 = s4; 也存在类似问题

存在问题:1.同一对象析构多次,程序崩溃;2.内存泄漏
那么该如何解决出现的问题呐?这需要我们自己重新定义相关类的成员函数,下面将介绍多种深拷贝方法以解决此问题
我们已经知道了浅拷贝存在的问题,即多次析构同一空间。这个问题是类的成员函数引起的,就是前面浅拷贝里模拟实现的编译器自动合成的函数,确切的说,浅拷贝里的问题是由隐式拷贝构造函数和隐士赋值运算符引起的。
拷贝构造函数用于将一个对象拷贝到新创建的对象中。也就是说,他用于初始化过程中,最常见的是将新对象显式地初始化为现有的对象。每当程序生成了副本对象时,编译器也将使用拷贝构造函数。默认的拷贝构造函数逐个的拷贝非静态成员(即浅拷贝),拷贝的是成员的值。(由于按值传递对象将调用拷贝构造函数,因此应该按引用传递对象。这样可以节省调用构造函数的时间以及存储新对象的空间。)
默认的赋值运算符是通过自动为类重载赋值运算符实现的。它的原型是:Class_name & Class_name ::operator=(const Class_name &);它接受并返回一个指向类对象的引用。将已有的对象赋给另一个对象时,将使用重载的赋值运算符(初始化对象时不一定会使用)。如:String s1=s2;也可能分两步来处理这条语句:使用拷贝构造函数创建一个临时对像,然后通过赋值将临时对象的值复制到新对象中。即初始化总会调用拷贝构造函数,而使用“=“时也可能调用赋值运算符。
2.解决方法:深度复制(deep copy)。定义一个显式拷贝构造函数,拷贝字符串并将副本的地址赋给str成员,而不仅仅是拷贝字符串地址。这样每个对象都有自己的字符串,而不是引用另一个对象的字付串。则调用析构函数都将释放不同的字符串而不是去释放已经释放的字符串。代码如下:
1 String(const String& s)
2 :_pStr(new char[strlen(s._pStr)] + 1)
3 {
4 if (this != &s)
5 {
6 strcpy(_pStr, s._pStr);
7 }
8 }
必须定义拷贝构造函数的原因在于,一些类成员是使用new初始化的、指向数据的指针,而不是数据本身。
总结:如果类中包含了使用new初始化的指针成员,应当定义一个拷贝构造函数,以拷贝指向的数据,而不是指针;浅拷贝仅浅浅的拷贝指针信息,而不会深入”挖掘“以拷贝指针引用的结构。
默认赋值运算符存在的问题与默认拷贝构造函数相同:数据受损。试图删除已经删除的数据导致的结果是不正确的,因此可能改变内存中的内容,导致程序异常终止。解决方法是提供深度拷贝的赋值运算符定义。
1 String& operator=(const String& s)
2 {
3 if (this != &s)
4 {
5 char *temp = new char[strlen(s._pStr) + 1];
6 strcpy(temp, s._pStr);
7 delete[] _pStr;
8 _pStr = temp;
9 }
10 return *this;
11 }
注意:
•由于目标对象可能引用了以前分配的数据,所以函数应使用delete[]来释放这些数据。
•函数应该避免将对象赋值给本身;否则,给对象重新赋值前,释放内存操作可能删除对象的内容,
•函数返回一个指向调用对象的引用。
按照之前讨论的方法重新定义赋值运算符和拷贝构造函数,还有更简洁的方法如下:
1 String(const String& s)
2 :_pStr(NULL) //
3 {
4 String strtemp(s._pStr);
5 std::swap(_pStr, strtemp._pStr);
6 }
7
8 String& operator=(const String& s)
9 {
10 if (this != &s)
11 {
12 String strtemp(s);
13 std::swap(_pStr, strtemp._pStr);
14 }
15 return *this;
16 }
17
18 String& operator=(String s)
19 {
20 std::swap(_pStr, s._pStr);
21 return *this;
22 }
此外,还有一些深度拷贝的不同方法将在下一篇介绍。
String 类的实现(1)浅拷贝存在的问题以及深拷贝实现的更多相关文章
- String 类的实现(1)浅拷贝存在的问题
浅拷贝 : 也称位拷贝 , 编译器只是直接将指针的值拷贝过来, 结果多个对象共用 同 一块内 存, 当一个对象将这块内 存释放掉之后, 另 一些对象不知道该块空间 已经还给了 系 统, 以 为还有效, ...
- 自己实现简单的string类
1.前言 最近看了下<C++Primer>,觉得受益匪浅.不过纸上得来终觉浅,觉知此事须躬行.今天看了类类型,书中简单实现了String类,自己以前也学过C++,不过说来惭愧,以前都是用C ...
- String类的写时拷贝
#include<iostream>using namespace std; class String;ostream& operator<<(ostream & ...
- String 类 Copy-On-Write 技术以及使用时存在的风险
先来看一下string 面试时的简易写法(使用的是深拷贝): class String { String() :str(]) { str[] = '\0'; } String(char* p, siz ...
- String 类的实现(3)引用计数实现String类
我们知道在C++中动态开辟空间时是用字符new和delete的.其中使用new test[N]方式开辟空间时实际上是开辟了(N*sizeof(test)+4)字节的空间.如图示其中保存N的值主要用于析 ...
- String类的实现(4)写时拷贝浅析
由于释放内存空间,开辟内存空间时花费时间,因此,在我们在不需要写,只是读的时候就可以不用新开辟内存空间,就用浅拷贝的方式创建对象,当我们需要写的时候才去新开辟内存空间.这种方法就是写时拷贝.这也是一种 ...
- String 类的实现(2)引用计数与写时拷贝
1.引用计数 我们知道在C++中动态开辟空间时是用字符new和delete的.其中使用new test[N]方式开辟空间时实际上是开辟了(N*sizeof(test)+4)字节的空间.如图示其中保存N ...
- String 类实现 以及>> <<流插入/流提取运算符重载
简单版的String类,旨在说明>> <<重载 #include <iostream> //#include <cstring>//包含char*的字符 ...
- (C++)已知String类的定义,实现其函数体
CString类的定义如下: class CMyString{ public: CMyString(const char* pData=NULL); CMyString(const CMyString ...
随机推荐
- 【防坑指南】nginx重启后出现[error] open() “/usr/local/var/run/nginx/nginx.pid” failed
重新启动nginx后,出现报错,原因就是下没有nginx文件夹或没有nginx.pid文件,为什么会没有呢? 原因就是每次重新启动,系统都会自动删除文件,所以解决方式就是更改pid文件存储的位置, 打 ...
- 20165221 JAVA第三周学习心得
知识点回顾 类与对象学习总结 类:java作为面向对象型语言具有三个特性:①封装性.②继承性.③多态性.java中类是基本要素,类声明的变量叫对象.在类中定义体的函数题叫方法. 类与程序的基本结构: ...
- 十分钟入门 Less
这篇文章来自 Danny Markov, 是我最喜欢的博主之一,实际上我最近翻译的一些文章全是出自他手.在查看本文之前你也可以 查看原文. 我们都知道写 CSS 代码是有些枯燥无味的,尤其是面对那些成 ...
- 【CentOS7.0】虚拟机如何实现扩展存储空间
写在前面的一些小废话 有时候,虚拟机随着使用,会导致存储不够,这时就需要给虚拟机扩容. 扩容的前提是,此虚拟机没有快照. 扩容时,需要编辑虚拟机设置.为了形成对比,第一张图片是扩容前,第二张图片是扩容 ...
- vue之router学习笔记
1.动态路由匹配 我们经常需要将具有给定模式的路线映射到同一个组件.例如,我们可能有一个User应该为所有用户呈现但具有不同用户ID的组件.在vue-router我们可以在路径中使用动态段以实现: c ...
- DHCP Server (推荐使用Windows)
一些小的服务 windows做的比linux好 DHCP服务概述: 名称:DHCP (Dynamic Host Configuration Protocol --动态主机配置协议) 功能:是一个局域网 ...
- windows下揪出java程序占用cpu很高的线程
背景 天天搞java,这些监控也都知道,用过,但也没往细里追究.因为也没碰见这种问题,这次还是静下来走一遍流程吧.与网上基本一致,不过我区分了下linux和windows的不一样.我感觉基本是程序写成 ...
- 快速安装freeswitch
前不久在Centos 6.4上安装了一台Freeswitch,测试已经OK.为了测试FS 的集群效果,从新在安装一台FS,快速安装的过程如下: 方案一:快速安装前提:不用重新下载Freeswitch. ...
- Linux学习之CentOS(一)--CentOS6.4环境搭建
Linux学习之CentOS(一)--CentOS6.4环境搭建http://www.cnblogs.com/xiaoluo501395377/archive/2013/03/31/CentOs.ht ...
- Protobuf3 语法指南
目录 [−] 定义一个消息类型 指定字段类型 分配标识号 指定字段规则 添加更多消息类型 添加注释 保留标识符(Reserved) 从.proto文件生成了什么? 标量数值类型 默认值 枚举 使用其他 ...