深浅拷贝和写时拷贝

来源:互联网 发布:网络暴力典型案例中国 编辑:程序博客网 时间:2024/05/13 04:37
拷贝:  
     事实是,在对象拷贝过程中,如果没有自定义拷贝构造函数系统会提供一个缺省的拷贝构造函数,缺省的拷贝构造函数对于基本类型的成员变量,按字节复制,对于类类型成员变量,调用其相应类型的拷贝构造函数。

浅拷贝:
      缺省拷贝构造函数在拷贝过程中是按字节复制的,对于指针型成员变量只复制指针本身,而不复制指针所指向的目标--浅拷贝。

浅拷贝的代码
class String{public:       String(const char* str = "")              :_str(new char[strlen(str) + 1])       {              strcpy(_str, str);       }       //拷贝构造函数,它是构造函数的重载       String(const String& s)       {              //浅拷贝的情况下,只复制了指针本身,对指针所指向的内存并没有进行拷贝              _str = s._str;       }       String& operator=(const String& s)       {              if (this != &s)              {                     _str = s._str;              }              return *this;       }       ~String()       {              if (_str)              {                     //指针指向相同的内存空间,再次释放一块已经释放的空间将会出现错误                     delete[] _str;              }       }private:       char* _str;};



浅拷贝出现的问题:
     (1)在进行对象复制后,事实上s1、s2里的成员指针m_psz都指向了一块内存空间(即内存空间共享了),在s1析构时,delete了成员指针m_psz所指向的内存空间,而s2析构时同样指向(此时已变成野指针)并且要释放这片已经被s1析构函数释放的内存空间,这就让同样一片内存空间出现了重复释放 ,从而出错。
     (2)而浅拷贝还存在着一个问题,因为一片空间被两个不同的子对象共享了,只要其中的一个子对象改变了其中的值,那另一个对象的值也跟着改变了

深拷贝:
     为每个对象都分配内存空间,防止析构时出现错误。

深拷贝的代码:
class String{public:       String(const char* str = "")              :_str(new char[strlen(str) + 1])       {              strcpy(_str, str);       }       String(const String& s)       {              //内存的拷贝先要创建出一块内存              _str = new char[strlen(s._str)+1];              //然后将指针所指向的内容拷贝一份              strcpy(_str, s._str);       }       //赋值运算符的重载       String& operator=(const String& s)       {              //防止自身对自身赋值(因为会delete掉原来的内存空间,如果是自己给自己赋值,那么delete以后就找不到了)              if (this != &s)              {                     delete[] _str;                     _str = new char[strlen(s._str) + 1];                     strcpy(_str, s._str);              }              return *this;       }       ~String()       {              if (_str)              {                     delete[] _str;              }       }private:       char* _str;};



深浅拷贝的不同:
     深浅拷贝的主要区别在于是否共用空间。两者的代码只有拷贝构造函数和赋值运算符重载时,深拷贝需要重新开辟空间,而浅拷贝仅仅改变了指针的指向。

深拷贝的缺点:
     重复的去开辟空间和释放空间效率是很低下的。

写时拷贝的引入:
     大家都知道,重复的开辟空间和释放内存是很耗时的,效率也很低下,相对于写时拷贝,深拷贝就很耗费时间了,效率自然没有写时拷贝好。
     写时拷贝是用一个计数器记住当前空间被多少个指针所指向,每次调用析构函数的时候,只需要看看自减后引用计数是否为零(是否只被最后一个指针所指向)。如果是,则销毁空间,如果不是,那么只需要引用计数减减即可,无需释放空间。
     写时拷贝是一种高性能的写法,不过这种写法如果不注意就会把引用计数弄错,导致错误。所以在写时拷贝的写法的时候,一定要小心改写引用计数。

执行下面的一段代码,写时拷贝写法的指针应该如何指向呢?
void test(){       String s1("hello");       String s2(s1);       String s3("world");       s1 = s3;}


图解上面的代码:

写时拷贝的代码:
class String{public:       String(const char* str = "")              :_str(new char[strlen(str)+1+4])//+1表示字符串后面要放一个'\0',+4表示多开辟一个空间存放引用计数       {              _str += 4;//_str指向数据存放区              strcpy(_str, str);              _GetCount() = 1;//刚开始这里写成了_GetCount++,其实是不对的,因为引用计数所指向的这a块空间并没有初始化,他的值并不是0,是一个随机值,随机值++还是随机值       }       String(const String& s)              :_str(s._str)       {              _GetCount()++;       }       String& operator=(String& s)       {              if (this != &s)              {                     if (--_GetCount() == 0)                     {                           delete[] (_str-4);                     }                     ++s._GetCount();                     _str = s._str;              }              return *this;       }       ~String()       {              if (--_GetCount() == 0)              {                     delete[] (_str-4);              }       }public:       int& _GetCount()       {              return *((int*)_str-1);       }private:       char* _str;};


    

原创粉丝点击