operator=与复制操作

来源:互联网 发布:盘石网络 编辑:程序博客网 时间:2024/06/01 07:19

4.3复制操作

                                                让你陷入麻烦的并非你所不知的,

                                                而是你所确信的并非如你所知。

                                                                  --马克.吐温

                                               每个复杂问题都有一个清晰、简单但错误的答案。

                                                                                                                                                    --亨利.路易斯.门肯

                                               要想让别人听得明白,言辞必须简洁。

                                                                         --西塞罗

在复制对象前,应了解对象数据成员的值的构成。对象的存储单元不仅是值的内存类型分配单元,而且包括动态内存,原因是指针(资源句柄)与所标识的内存单元相分离。因此表达式x=y在复制时,不仅要拷贝对象y的静态值,而且对象x要申请新的动态内存,拷贝y的动态值。

因此,拷贝一个对象时,必须满足两个准则:

(1)左值对象和右值对象的独立性。在x=y操作之后,对x的操作不会隐藏地改变y的值。对象x,y的静态值和动态内存单元都彼此独立,xy各自有全面而且独立的地址单元范围。因此,对象xy不能有指针指向相同的动态单元。

(2)左值对象和右值对象复制后的等价性。在x=y之后,对xy执行相同的操作或普通函数,得到的结果应相同。

拷贝的实现方法有浅拷贝(或位拷贝)和深拷贝。编译器默认实体

浅拷贝(shallow copy)将使两个对象处于共享状态(shared state)。浅拷贝是按数据成员复制,实际上是对象的数据成员值的复制,而资源没有分配。若数据成员动态分配内存,则出现左值和右值对象x,y都有一个标识相同数据的指针(地址),即xy在共享状态。因此,左值对象x修改指针所标识的数据,则右值对象y将反映这个修改。这就是拷贝后的不独立。浅拷贝将产生两次(多次)删除和悬挂指针(darling pointer)的问题,还可能出现内存泄漏的问题。但是在多线程中,对象利用同步机制共享数据和资源,因此此时的共享状态是有益的,可用引用计数(或共享数据标志)和写前拷贝解决重复删除问题。

浅拷贝实现方式中的不安全允许类提供显式(explicit)的复制成员函数。深拷贝(deep copy)是对象的所有相关内存和资源的复制,可防止浅拷贝的所有问题。

4.3.1复制构造函数    

4.3.2复制赋值运算符

1.operator=

C++中没有追加和修补动态内存的概念,若一个对象执行operator=,将改写对象的旧值,必须先释放所有动态内存,再分配资源句柄成员的新的内存单元。所以赋值操作分配新的资源和数据,使对象“再生”。显式定义或重载operator=,先析构旧资源可防止内存泄漏(图1),再复制分配独立的新资源可防止两次删除(2)。因为此机制,operator=不能出现自赋值,不能对删除的数据进行拷贝。

operator=的形参类型是对象引用的常量,而且返回类型是同类对象的引用。operator=只有一个形参,是赋值表达式右值对象的引用,然而返回值类型也只能是引用。原因是若将operator=的返回值作为复制复制操作符函数的参数,则返回值必须是对象引用,这是返回值和返回引用不同的问题。例如x=y=z,而operator=是右边优先,因此y=z的返回值是y的引用,应注意不是z的引用。若不返回引用则xoperator=的实现语法是:thing& operator=(const thing&)

算法CA(复制赋值操作符)这个算法的输入是右值对象的引用,将实参的数据成员值和完整的资源句柄的内容复制到左值对象,输出左值对象this的引用。

    CA1[防止自赋值]条件语句判断this是否与参数对象相等。

    CA2[析构旧资源] delete内存单元或回收旧的资源句柄。

CA3[复制生成新的资源和数据] new申请分配内存单元,复制右

    值对象的数据成员和资源句柄的内容。

2.示例

此例是复制二维数组。电子表格是一个由“单元格”(cell)组成的二维表格,每个单元格包含一个数字或字符串。如Mocrosoft Excel提供了完成数学运算的功能。此例并不打算和微软抢市场份额,只是用来说明operator=的用法。

电子表格应用使用了两个基本的类:SpreadsheetSpreadsheetCell。每个Spreadsheet对象包含多个SpreadsheetCell对象。另外,一个SpreadsheetApplication类管理多个Spreadsheet类。

SpreadsheetSpreadsheetCell的一个二维数组,提供了方法设置和获取Spreadsheet中特定位置的单元格,在两个方向都使用数字。

#includeSpreadsheetCell.h

class Spreadsheet

{

   public:

       Spreadsheet(int inwidth,int inWtight);

       ~Spreadsheet();

       Spreadsheet Spreadsheet(const Spreadsheet& src);

       Spreadsheet& operator=(const Spreadsheet& rhs);

       void setCellAt(int x,int y,const SpreadsheetCell& cell);

       SpreadsheetCell getCellAt(int x,int y);//x,y表示两个方向

   protected:

       bool inRange(int val,int upper);

       int mWidth,mHeight;

       SpreadsheetCell **mCells;//mCells相当于二维数组

}

以下是复制构造函数的定义。复制构造函数必须先申请分配动态内存。mCells二维数组是mWidth个一维数组构成,每个一维数组用mCells[i]表示。每个一维数组有相同的mHeight个元素,每个元素用mCells[i][j]表示。mCells[k]表示第(k-1)个一维数组,包括所有mHeight个元素{mCells[k][0],...,mCells[k][mHeight-1]}。指针mCells表示所有一维数组的资源句柄组成的一维数组,mCells指针仍然是一维数组,有mWeight个元素,似乎与C语言不同。见图3。可知,C++的二维指针和二维数组的准则相同,都是一维数组的元素个数必须相同。其次用深复制方式,按元素复制,用两层循环访问每一个元素。

Spreadsheet Spreadsheet(const Spreadsheet& src)//复制构造函数

{

  int i,j;

  mWidth=src.mWidth;

  mHeight=src.mHeight;

  mCells=new SpreadsheetCells*[mWidth];

                  //申请一维数组元素个数mWidth

  for(i=0;i<mWidth;i++)//每一个一维数组申请mHeight个元素

     mCells[i]=new SpreadsheetCells[mHeight];

for(i=0;i<mWidth;i++)//二维数组按元素复制,访问每一个元素

   for(j=0;j<mHeight;i++)

     mCells[i][j]=src.mCells[i][j];

}

以下是Spreadsheet类赋值操作符的实现。赋值时,每一个对象应已经得到初始化。所以,在分配新资源前,必须先释放所有动态内存,再分配新资源。

Spreadsheet& operator=(const Spreadsheet& rhs);

{

  int i,j;

  if(this=&rhs)  //自复制

     return(*this);

 for(i=0;i<mWidth;i++)//收回mWidth个一维数组

     delete[] mCell[i];

delete[] mCells;//收回mCells二维数组的所有资源句柄

mWidth=rhs.mWidth;//以下源码与复制构造函数相同

mHeight=rhs.mHeight;

mCells=new SpreadsheetCells*[mWidth];

for(i=0;i<mWidth;i++)

   mCells[i]=new SpreadsheetCells[mHeight];

for(i=0;i<mWidth;i++)

   for(j=0;j<mHeight;i++)

     mCells[i][j]=rhs.mCells[i][j];

return (*this);//必须返回,operator=并不仅仅是给成员数据赋值

}