4.7. Mastering Copy Semantics: ICloneable

When we copy one reference type with another, as in the following example:

Matrix mat = new Matrix( 4, 4 );
// ...
Matrix mat2 = mat;

the result is a shallow copy; that is, both mat and mat2 now refer to the same Matrix object on the managed heap. A problem occurs when the object is modified through one handle, as in this example:

// changes are seen through mat2 as well
mat[0,0]=mat[1,1]=mat[2,2]=mat[3,3]=1;

while the second handle still requires the object to be in its original state.

If we are users, we cannot modify the class implementation to provide deep-copy semantics. Rather we'll need to explicitly implement a deep copy. First we allocate a new instance of the reference types. Next we copy each value in turn:

public class DeepCopy
{
      public static Matrix copyMatrix( Matrix m )
      {
            Matrix mat = new Matrix( m.Rows, m.Cols );

            for ( int ix = 0; ix < m.Rows; ++ix )
               for ( int iy = 0; iy < m.Cols; ++iy )
                  mat[ix,iy] = m[ix,iy];

            return mat;
      }
}

Matrix mat = new Matrix( 4, 4 );
Matrix mat2 = DeepCopy.copyMatrix( mat );

The result is a second, independent copy of the object. We have cloned it.

By default, the copy of a reference type results in a shallow copy. If we are designers of a class, we need to think about whether we wish to also provide deep-copy semantics. We do that by implementing the ICloneable interface. ICloneable specifies a single function, Clone(). Clone() returns a deep copy as an instance of type object—for example:

class matrix : ICloneable
{
      public matrix( int row, int col )
      {
            m_row = ( row <= 0 ) ? 1 : row;
            m_col = ( col <= 0 ) ? 1 : col;

            m_mat = new double[ m_row, m_col ];
      }
      public object Clone()
      {
            matrix mat = new matrix(m_row,m_col);
            for ( int ix = 0; ix < m_row; ++ix )
                  for ( int iy = 0; iy < m_col; ++iy )
                        mat.m_mat[ ix, iy ] = m_mat[ ix, iy ];
            return mat;
      }
}

The user now has a choice of using a shallow or a deep copy. Knowing when to choose which one is really what's important here. Consider the following overloaded addition operator:

public static matrix operator+( matrix m1, matrix m2 )
{
    check_both_rows_cols( m1, m2 );

    // not: matrix mat = m1;
    matrix mat = (matrix) m1.Clone();

    for ( int ix = 0 ; ix < m1.rows; ix++ )
       for ( int ij = 0; ij < m1.cols; ij++ )
             mat[ ix, ij ] += m2[ ix, ij ];

    return mat;
}

There are four copies of a matrix. Of those four, only one needs a deep copy.

The default shallow copy is the right mechanism for passing the two matrix objects into the function and returning the resulting matrix object. A deep copy in this case would be unnecessary and inefficient.

On the other hand, a shallow copy inside the addition operator is a serious mistake. Initializing m1 to the local matrix through a shallow copy means that each assignment to mat modifies m1 as well. This is where a deep copy is necessary to keep the two objects independent.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.213.212