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.
18.191.237.79