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)浅拷贝存在的问题以及深拷贝实现的更多相关文章

  1. String 类的实现(1)浅拷贝存在的问题

    浅拷贝 : 也称位拷贝 , 编译器只是直接将指针的值拷贝过来, 结果多个对象共用 同 一块内 存, 当一个对象将这块内 存释放掉之后, 另 一些对象不知道该块空间 已经还给了 系 统, 以 为还有效, ...

  2. 自己实现简单的string类

    1.前言 最近看了下<C++Primer>,觉得受益匪浅.不过纸上得来终觉浅,觉知此事须躬行.今天看了类类型,书中简单实现了String类,自己以前也学过C++,不过说来惭愧,以前都是用C ...

  3. String类的写时拷贝

    #include<iostream>using namespace std; class String;ostream& operator<<(ostream & ...

  4. String 类 Copy-On-Write 技术以及使用时存在的风险

    先来看一下string 面试时的简易写法(使用的是深拷贝): class String { String() :str(]) { str[] = '\0'; } String(char* p, siz ...

  5. String 类的实现(3)引用计数实现String类

    我们知道在C++中动态开辟空间时是用字符new和delete的.其中使用new test[N]方式开辟空间时实际上是开辟了(N*sizeof(test)+4)字节的空间.如图示其中保存N的值主要用于析 ...

  6. String类的实现(4)写时拷贝浅析

    由于释放内存空间,开辟内存空间时花费时间,因此,在我们在不需要写,只是读的时候就可以不用新开辟内存空间,就用浅拷贝的方式创建对象,当我们需要写的时候才去新开辟内存空间.这种方法就是写时拷贝.这也是一种 ...

  7. String 类的实现(2)引用计数与写时拷贝

    1.引用计数 我们知道在C++中动态开辟空间时是用字符new和delete的.其中使用new test[N]方式开辟空间时实际上是开辟了(N*sizeof(test)+4)字节的空间.如图示其中保存N ...

  8. String 类实现 以及>> <<流插入/流提取运算符重载

    简单版的String类,旨在说明>> <<重载 #include <iostream> //#include <cstring>//包含char*的字符 ...

  9. (C++)已知String类的定义,实现其函数体

    CString类的定义如下: class CMyString{ public: CMyString(const char* pData=NULL); CMyString(const CMyString ...

随机推荐

  1. Qemu-KVM管理

    内容: 一.KVM基本配置 二.KVM网络的桥接 三.创建虚拟机 四.虚拟机的关闭和启动 关于KVM: 1).KVM是开源软件,全称是kernel-based virtual machine(基于内核 ...

  2. 欢迎使用 MWeb-Test

    首先介绍一下 MWeb 是什么,MWeb 是专业的 Markdown 写作.记笔记.静态博客生成软件. 然后这里重点说明一下:MWeb 有两个模式,外部模式和文档库模式.外部模式中把本地硬盘或 Dro ...

  3. 通过URL传递中文参数的乱码处理

    环境:web.xml中配置了 <filter> <filter-name>encodingFilter</filter-name> <filter-class ...

  4. unit test

    1) State vs Behaviour Verificationhttps://manas.tech/blog/2009/04/30/state-vs-behaviour-verification ...

  5. JDK的安装及环境变量配置

    JDK的安装及环境变量配置 JDK解释:直达详细解释. 1.JDK下载地址:点击直达官网下载 进入后,如图1,点击图中红框DOWNLOAD按钮进入下载页 进入下载页后,在下载也底端,根据自己的需求下载 ...

  6. 前端 ---- js 模拟百度导航栏滚动案例

    模拟百度导航栏滚动监听 代码如下: <!DOCTYPE html> <html lang="en"> <head> <meta chars ...

  7. 【原创】大数据基础之Flume(2)kudu sink

    kudu中的flume sink代码路径: https://github.com/apache/kudu/tree/master/java/kudu-flume-sink kudu-flume-sin ...

  8. 【原创】大数据基础之Benchmark(1)HiBench

    HiBench 7官方:https://github.com/intel-hadoop/HiBench 一 简介 HiBench is a big data benchmark suite that ...

  9. 分布式全文检索引擎之ElasticSearch

    一 什么是 ElasticSearch Elasticsearch 是一个分布式可扩展的实时搜索和分析引擎,一个建立在全文搜索引擎 Apache Lucene(TM) 基础上的搜索引擎.当然 Elas ...

  10. oracle存储大文本clob、blob

    oracle存储大文本clob.blob 1 package cn.itcast.web.oracle.util; 2 3 import java.sql.Connection; 4 import j ...