I l@ve RuBoard Previous Section Next Section

Exercise 6.2

Reimplement the Matrix class of Exercise 5.3 as a template. In addition, extend it to support arbitrary row and column size using heap memory. Allocate the memory in the constructor and deallocate it in the destructor.

The primary work of this exercise is to add general row and column support. We introduce a constructor that takes a row and a column size as arguments. The body of the constructor allocates the necessary memory from the program's free store:



Matrix( int rows, int columns ) 


      : _rows( rows ), _cols( columns ) 


{ 


    int size = _rows * _cols; 


    _matrix = new elemType[ size ]; 


    for ( int ix = 0; ix < size; ++ix ) 


          _matrix[ ix ] = elemType(); 


} 

elemType is the template parameter. _matrix is now an elemType pointer that addresses the heap memory allocated through the new expression.



template <typename elemType> 


class Matrix { 


public: 


    // ... 


private: 


    int       _rows; 


    int       _cols; 


    elemType *_matrix; 


}; 

It is not possible to specify an explicit initial value for which to set each element of the Matrix. The potential actual types that might be specified for elemType are simply too diverse. The language allows us, rather, to specify a default constructor:



_matrix[ ix ] = elemType(); 

For an int, this becomes int() and resolves to 0. For a float, this becomes float() and resolves to 0.0f. For a string, this becomes string() and invokes the default string constructor, and so on.

We must add a destructor to delete the heap memory acquired during the class object's construction:



~Matrix(){ delete [] _matrix; } 

We also must provide a copy constructor and a copy assignment operator now. Default memberwise initialization and copy are no longer sufficient when we allocate memory in a constructor and deallocate it in a destructor. Under the default behavior, the _matrix pointer member of both class objects now addresses the same heap memory. This becomes particularly nasty if one object is destroyed while the other object continues to be active within the program: Its underlying matrix has been deleted! One solution is to deep copy the underlying matrix so that each class object addresses a separate instance:



template <typename elemType> 


Matrix<elemType>::Matrix( const Matrix & rhs ) 


{ 


  _rows = rhs._rows; _cols = rhs._cols; 


  int mat_size = _rows * _cols; 


  _matrix = new elemType[ mat_size ]; 


  for ( int ix = 0; ix < mat_size; ++ix ) 


        _matrix[ ix ] = rhs._matrix[ ix ]; 


} 





template <typename elemType> 


Matrix<elemType>& Matrix<elemType>::operator=( const Matrix &rhs ) 


{ 


   if ( this != &rhs ){ 


        _rows = rhs._rows; _cols = rhs._cols; 


        int mat_size = _rows * _cols; 


        delete [] _matrix; 


        _matrix = new elemType[ mat_size ]; 


        for ( int ix = 0; ix < mat_size; ++ix ) 


              _matrix[ ix ] = rhs._matrix[ ix ]; 


    } 





    return *this; 


} 

Here is the full class declaration and the remaining member functions:



#include <iostream> 


template <typename elemType> 


class Matrix 


{ 


    friend Matrix<elemType> 


        operator+( const Matrix<elemType>&, const Matrix<elemType>& ); 





    friend Matrix< elemType > 


        operator*( const Matrix<elemType>&, const Matrix<elemType>& ); 





public: 


    Matrix( int rows, int columns ); 


    Matrix( const Matrix& ); 


    ~Matrix(); 


    Matrix& operator=( const Matrix& ); 





    void operator+=( const Matrix& ); 


    elemType& operator()( int row, int column ) 


        { return _matrix[ row * cols() + column ]; } 





    const elemType& operator()( int row, int column ) const 


        { return _matrix[ row * cols() + column ]; } 





    int rows() const { return _rows; } 


    int cols() const { return _cols; } 





    bool same_size( const Matrix &m ) const 


         { return rows() == m.rows() && cols() == m.cols(); } 





    bool comfortable( const Matrix &m ) const 


         { return ( cols() == m.rows() ); } 





    ostream& print( ostream& ) const; 





protected: 


    int  _rows; 


    int  _cols; 


    elemType *_matrix; 


}; 





template <typename elemType> 


inline ostream& 


operator<<( ostream& os, const Matrix<elemType> &m ) 


    { return m.print( os ); } 





// end of Matrix.h 





template <typename elemType> 


Matrix< elemType > 


operator+( const Matrix<elemType> &m1, const Matrix<elemType> &m2 ) 


{ 


    // make sure m1 & m2 are same size 


    Matrix<elemType> result( m1 ); 


    result += m2; 


    return result; 


} 





template <typename elemType> 


Matrix<elemType> 


operator*( const Matrix<elemType> &m1, const Matrix<elemType> &m2 ) 


{ 


    // m1's columns must equal m2's rows ... 


    Matrix<elemType> result( m1.rows(), m2.cols() ); 


    for ( int ix = 0; ix < m1.rows(); ix++ ) { 


        for ( int jx = 0; jx < m1.cols(); jx++ ) { 


            result( ix, jx ) = 0; 


            for ( int kx = 0; kx < m1.cols(); kx++ ) 


                result( ix, jx ) += m1( ix, kx ) * m2( kx, jx ); 


        } 


    } 


    return result; 


} 





template <typename elemType> 


void Matrix<elemType>::operator+=( const Matrix &m ){ 


    // make sure m1 & m2 are same size 


    int matrix_size = cols() * rows(); 


    for ( int ix = 0; ix < matrix_size; ++ix ) 


        ( *( _matrix + ix )) += ( *( m._matrix + ix )); 


} 





template <typename elemType> 


ostream& Matrix<elemType>::print( ostream &os ) const { 


    int col = cols(); 


    int matrix_size = col * rows(); 


    for ( int ix = 0; ix < matrix_size; ++ix ){ 


        if ( ix % col == 0 ) os << endl; 


        os << ( *( _matrix + ix )) << ' '; 


    } 


    os << endl; 


    return os; 


} 

Here is a small program to exercise our Matrix class template:



int main() 


{ 


   ofstream log( "C:\\My Documents\\log.txt" ); 


   if ( ! log ) 


      { cerr << "can't open log file!\n"; return; } 





   Matrix<float> identity( 4, 4 ); 


   log << "identity: " << identity << endl; 


   float ar[16]={ 1., 0., 0., 0., 0., 1., 0., 0., 


                  0., 0., 1., 0., 0., 0., 0., 1. }; 





   for ( int i = 0, k = 0; i < 4; ++i ) 


         for ( int j = 0; j < 4; ++j ) 


               identity( i, j ) = ar[ k++ ]; 


   log <<  "identity after set: " << identity << endl; 





   Matrix<float> m( identity ); 


   log << "m: memberwise initialized: " << m << endl; 





   Matrix<float> m2( 8, 12 ); 


   log << "m2: 8x12: " <<  m2  << endl; 


   m2 = m; 


   log << "m2 after memberwise assigned to m: " 


       << m2 << endl; 





   float ar2[16]={ 1.3, 0.4, 2.6, 8.2, 6.2, 1.7, 1.3, 8.3, 


                   4.2, 7.4, 2.7, 1.9, 6.3, 8.1, 5.6, 6.6 }; 





   Matrix<float> m3( 4, 4 ); 


   for ( int ix = 0, kx = 0; ix < 4; ++ix ) 


          for ( int j = 0; j < 4; ++j ) 


                 m3( ix, j ) = ar2[ kx++ ]; 





   log << "m3: assigned random values: " << m3 << endl; 





   Matrix<float> m4 = m3 * identity; log << m4 << endl; 


   Matrix<float> m5 = m3 + m4; log << m5 << endl; 





   m3 += m4; log << m3 << endl; 


} 

When compiled and executed, the program generates the following output:



identity: 


0 0 0 0 


0 0 0 0 


0 0 0 0 


0 0 0 0 





identity after set: 


1 0 0 0 


0 1 0 0 


0 0 1 0 


0 0 0 1 





m: memberwise initialized: 


1 0 0 0 


0 1 0 0 


0 0 1 0 


0 0 0 1 





m2: 8x12: 


0 0 0 0 0 0 0 0 0 0 0 0 


0 0 0 0 0 0 0 0 0 0 0 0 


0 0 0 0 0 0 0 0 0 0 0 0 


0 0 0 0 0 0 0 0 0 0 0 0 


0 0 0 0 0 0 0 0 0 0 0 0 


0 0 0 0 0 0 0 0 0 0 0 0 


0 0 0 0 0 0 0 0 0 0 0 0 


0 0 0 0 0 0 0 0 0 0 0 0 





m2 after memberwise assigned to m: 


1 0 0 0 


0 1 0 0 


0 0 1 0 


0 0 0 1 





m3: assigned random values: 


1.3 0.4 2.6 8.2 


6.2 1.7 1.3 8.3 


4.2 7.4 2.7 1.9 


6.3 8.1 5.6 6.6 





1.3 0.4 2.6 8.2 


6.2 1.7 1.3 8.3 


4.2 7.4 2.7 1.9 


6.3 8.1 5.6 6.6 





2.6 0.8 5.2 16.4 


12.4 3.4 2.6 16.6 


8.4 14.8 5.4 3.8 


12.6 16.2 11.2 13.2 





2.6 0.8 5.2 16.4 


12.4 3.4 2.6 16.6 


8.4 14.8 5.4 3.8 


12.6 16.2 11.2 13.2 
    I l@ve RuBoard Previous Section Next Section