8.1 Custom Matrix Class Library
8.1.6 Mathematical Operators Implementation
The next part of the implementation concerns the methods overloading the binary operators that allow matrix algebra such as addition, subtraction and multiplication. There are two types of operators to be overloaded here. The first is operation without assignment. The second is operation with assignment. The first type of operator method creates a new matrix to store the result of an operation (such as addition), while the second type applies the result of the operation into the left-hand argument. For instance the first type will produce a new matrix C, from the equation C = A + B. The second type will overwrite A with the result of A + B.
The first operator to implement is for addition without assignment. A new matrix result is created with initial filled values equal to 0. Then each element is iterated through to be the pairwise sum of thethismatrix and the new right hand side matrixrhs. Notice that we use the pointer dereferencing syntax withthiswhen accessing the element values: this−>mat[i][j]. This is identical to writing (∗this). mat[i ][ j ]. We must dereference the pointer before we can access the underlying object. Finally, we return the result:
Note that this can be a particularly expensive operation. We are creating a new matrix for every call of this method. However, modern compilers are smart enough to make sure that this operation is not as performance heavy as it used to be, so for our current needs we are justified in creating the matrix here. Note again that if we were to return a matrix by reference and then create the matrix within the class via the newoperator, we would have an error as the matrix object would go out of scope as soon as the method returned.
// A d d i t i o n o f two m a t r i c e s
template<typename T>
QSMatrix<T> QSMatrix<T> : :operator+(const QSMatrix<T>& r h s ) { QSMatrix r e s u l t ( rows , c o l s , 0 . 0 ) ; f o r (unsigned i =0; i <rows ; i ++) { f o r (unsigned j =0; j <c o l s ; j ++) { r e s u l t ( i , j ) = t h i s−>mat [ i ] [ j ] + r h s ( i , j ) ; } } return r e s u l t ; }
85
DOES return a reference to an object, but this is fine since the object reference it returns is to this, which exists outside of the scope of the method. The method itself makes use of the
operator+= that is bound to the type object. Thus when we carry out the line this−>mat [i][j] += rhs(i,j) ; we are making use of the types own operator overload. Finally, we return a dereferenced pointer tothisgiving us back the modified matrix:
// C u m u l a t i v e a d d i t i o n o f t h i s m a t r i x and a n o t h e r
template<typename T>
QSMatrix<T>& QSMatrix<T> : :operator+=(const QSMatrix<T>& r h s ) {
unsigned rows = r h s . g e t r o w s ( ) ; unsigned c o l s = r h s . g e t c o l s ( ) ; f o r (unsigned i =0; i <rows ; i ++) { f o r (unsigned j =0; j <c o l s ; j ++) { t h i s−>mat [ i ] [ j ] += r h s ( i , j ) ; } } return ∗t h i s; }
The two matrix subtraction operators operator− and operator−= are almost identical to the addition variants, so I won’t explain them here. If you wish to see their implementation, have a look at the full listing below.
I will discuss the matrix multiplication methods though as their syntax is sufficiently different to warrant explanation. The first operator is that without assignment, operator∗. We can use this to carry out an equation of the form C = A × B. The first part of the method creates a new result matrix that has the same size as the right hand side matrix, rhs. Then we perform the triple loop associated with matrix multiplication. We iterate over each element in the result matrix and assign it the value of this−>mat[i][k] ∗ rhs(k, j ), i.e. the value of Aik × Bkj, for
k ∈ {0, ..., M − 1}:
// L e f t m u l t i p l i c a t i o n o f t h i s m a t r i x and a n o t h e r
template<typename T>
QSMatrix<T> QSMatrix<T> : :operator∗ (const QSMatrix<T>& r h s ) {
unsigned rows = r h s . g e t r o w s ( ) ;
unsigned c o l s = r h s . g e t c o l s ( ) ; QSMatrix r e s u l t ( rows , c o l s , 0 . 0 ) ;
f o r (unsigned i =0; i <rows ; i ++) { f o r (unsigned j =0; j <c o l s ; j ++) { f o r (unsigned k =0; k<rows ; k++) { r e s u l t ( i , j ) += t h i s−>mat [ i ] [ k ] ∗ r h s ( k , j ) ; } } } return r e s u l t ; }
The implementation of the operator∗=is far simpler, but only because we are building on what already exists. The first line creates a new matrix called result which stores the result of multiplying the dereferenced pointer tothisand the right hand side matrix,rhs. The second line then setsthisto be equal to the result above. This is necessary as if it was carried out in one step, data would be overwritten before it could be used, creating an incorrect result. Finally the referenced pointer tothisis returned. Most of the work is carried out by theoperator∗which is defined above. The listing is as follows:
// C u m u l a t i v e l e f t m u l t i p l i c a t i o n o f t h i s m a t r i x and a n o t h e r
template<typename T>
QSMatrix<T>& QSMatrix<T> : :operator∗=(const QSMatrix<T>& r h s ) { QSMatrix r e s u l t = ( ∗t h i s) ∗ r h s ;
( ∗t h i s) = r e s u l t ;
return ∗t h i s; }
We also wish to apply scalar element-wise operations to the matrix, in particular element-wise scalar addition, subtraction, multiplication and division. Since they are all very similar, I will only provide explanation for the addition operator. The first point of note is that the parameter is now a constT&, i.e. a reference to a const type. This is the scalar value that will be added to all matrix elements. We then create a new result matrix as before, of identical size tothis. Then we iterate over the elements of the result matrix and set their values equal to the sum of the individual elements ofthisand our type value, rhs. Finally, we return the result matrix:
// M a t r i x / s c a l a r a d d i t i o n
87
QSMatrix<T> QSMatrix<T> : :operator+(const T& r h s ) { QSMatrix r e s u l t ( rows , c o l s , 0 . 0 ) ; f o r (unsigned i =0; i <rows ; i ++) { f o r (unsigned j =0; j <c o l s ; j ++) { r e s u l t ( i , j ) = t h i s−>mat [ i ] [ j ] + r h s ; } } return r e s u l t ; }
We also wish to allow (right) matrix vector multiplication. It is not too different from the implementation of matrix-matrix multiplication. In this instance we are returning a std :: vector <T> and also providing a separate vector as a parameter. Upon invocation of the method we create a new result vector that has the same size as the right hand side, rhs. Then we perform a double loop over the elements of the this matrix and assign the result to an element of the
result vector. Finally, we return the result vector:
// M u l t i p l y a m a t r i x w i t h a v e c t o r
template<typename T>
s t d : : v e c t o r <T> QSMatrix<T> : :operator∗ (const s t d : : v e c t o r <T>& r h s ) { s t d : : v e c t o r <T> r e s u l t ( r h s . s i z e ( ) , 0 . 0 ) ; f o r (unsigned i =0; i <rows ; i ++) { f o r (unsigned j =0; j <c o l s ; j ++) { r e s u l t [ i ] = t h i s−>mat [ i ] [ j ] ∗ r h s [ j ] ; } } return r e s u l t ; }
I’ve added a final matrix method, which is useful for certain numerical linear algebra tech- niques. Essentially it returns a vector of the diagonal elements of the matrix. Firstly we create the result vector, then assign it the values of the diagonal elements and finally we return the
result vector:
template<typename T> s t d : : v e c t o r <T> QSMatrix<T> : : d i a g v e c ( ) { s t d : : v e c t o r <T> r e s u l t ( rows , 0 . 0 ) ; f o r (unsigned i =0; i <rows ; i ++) { r e s u l t [ i ] = t h i s−>mat [ i ] [ i ] ; } return r e s u l t ; }
The final set of methods to implement are for accessing the individual elements as well as getting the number of rows and columns from the matrix. They’re all quite simple in their implementation. They dereferencethis and then obtain either an individual element or some private member data:
// A c c e s s t h e i n d i v i d u a l e l e m e n t s
template<typename T>
T& QSMatrix<T> : :operator( ) (const unsigned& row , const unsigned& c o l ) {
return t h i s−>mat [ row ] [ c o l ] ; }
// A c c e s s t h e i n d i v i d u a l e l e m e n t s ( c o n s t )
template<typename T>
const T& QSMatrix<T> : :operator( ) (const unsigned& row , const unsigned& c o l )
const {
return t h i s−>mat [ row ] [ c o l ] ; }
// Get t h e number o f rows o f t h e m a t r i x
template<typename T>
unsigned QSMatrix<T> : : g e t r o w s ( ) const {
return t h i s−>rows ; }
// Get t h e number o f columns o f t h e m a t r i x
template<typename T>
89
return t h i s−>c o l s ; }