[comp.lang.c++] Beginners problems

dwelly@saddle-lk.cs.UAlberta.CA (Andrew Dwelly) (03/14/91)

I've just started using c++ and I have an example of code here that
I can't get to run, even though it seems to be legal c++. This is almost 
certainly a beginners mistake, but I can't for the life of me see what the
problem is.

The situation :

I've produced a class of dynamic matrices, with appropriate access, constructor
and destructor functions. Now I'm trying to get a simple matrix multiply to
work, overloading the * operator. In my main function, I create and print
out a matrix "m", then try to print out m*m;

What I think I'm doing :

I create an automatic matrix variable for my results, then perform
a simple multiply on the inputs, placing the result in the output
variable. Finally I return "out".

Since my function has been declared as returning a matrix, I understood that
a _copy_ of "out" was returned before the destructor was called on "out".

What actually happens :

Compiling with g++ on a Sparc produces the following results :-

saddle-lk% Matrix
1 2 
3 4 
 
2.24208e-44 2.16332e-40 
0 2.16355e-40 
Segmentation fault (core dumped)

Would someone take pity on me and tell me what I'm doing wrong ???

Andy Dwelly

-------------------------------------------------------------------------------

#include <stream.h>
#include <stddef.h>
#include <bool.h>

class matrix
{
    float **m;
    int   rows;
    int   cols;
public:

            matrix(int,int);
            ~matrix();
    float*& operator[](int);
    int     sizer();
    int     sizec();
    friend matrix 
            operator*(matrix&, matrix&);
};

// We want to be able to print it out

ostream& operator<<(ostream& s, matrix& m) // m must be a reference, why ?
{
    for (int i = 0; i < m.sizer(); i++)
    { 
        for (int j = 0; j < m.sizec(); j++)
        {
            s << m[i][j] << " ";
        }
        s << "\n";
    }
    return s;
}

matrix::matrix(int r, int c)
{
    rows = r;
    cols = c;
    m = new float*[r];
    for (int i = 0; i < r; i++)
    {
	m[i] = new float[c];
    }
}

matrix::~matrix()
{
    for (int i = 0; i < rows; i++)
    {
	delete m[i];
    }

    delete m;
}

float*&
matrix::operator[](int i)
{
    return m[i];
}

matrix::sizer()
{
    return rows;
}

matrix::sizec()
{
    return cols;
}

// Problems here ????

matrix
operator*(matrix& a, matrix& b)
{
    matrix out(a.sizer(), b.sizec());

    if (a.sizec() == b.sizer())
    {
	for (int i = 0; i < a.sizer(); i++)
	{
	    float *row = a.m[i];
	    float multsum;

	    for (int j = 0; j < b.sizec(); j++)
	    {
		multsum = 0.0;

		for (int k = 0; k < b.sizer(); k++)
		{
		    multsum += row[k] * b.m[k][j];
		}

		out.m[i][j] = multsum;
	    }
	}
    }
    return out;
}

main()
{
    matrix m(2,2);

    m[0][0] = 1.0;
    m[0][1] = 2.0;
    m[1][0] = 3.0;
    m[1][1] = 4.0;

    cout << m << "\n";

    cout << m * m << "\n";
}


--
******************************************************************************
Andy Dwelly : dwelly@cs.uablberta.ca, Tel: 403-492-7591

!@#$%$#, %^&*%, - Listen, who swears ? Christopher Robin has fallen downstairs.

mittle@blinn.watson.ibm.com (Josh Mittleman) (03/15/91)

The problem is that you haven't implemented the copy constructor, 

  matrix::matrix(const matrix&)

When your multiplication routine return out, what is returned is a
field-by-field copy of out.  It copies the value of each field; in
particular, it copies the value of the pointer out.m.  So, we have a new
matrix which points to the same double array of floats as out.  When you
exit matrix::operator*, out goes out of scope, and the destructor runs,
deleting the data now used by out and the return matrix.

Your lucky, by the way.  This kind of error can be a royal pain to
discover.  

You can solve your problem by adding matrix::matrix(const matrix&):

matrix::matrix(const matrix& M)
{
  rows = M.rows;
  cols = M.cols;
  m = new float*[rows];
  for (i = 0; i < rows; i++)
  {
    m[i] = new float[cols];
    for (int j = 0; j < cols; j++)
    {
      m[i][j] = M[i][j];
    }
  } 
}

You should also implement matrix::operator=(const matrix&), which will copy
the target matrix element by element, and will all deal with dimension
mismatch in some way.  Otherwise, a statement like:

   matrix m(2,2), n(2,2);
   ...
   m = n;

will copy field-by-field, not element-by-element.


You also asked:

ostream& operator<<(ostream& s, matrix& m) // m must be a reference, why ?

It doesn't have to be a reference, but it prevents an unnecessary copy
operation.  If you pass an object by value, then the function has to make a
temporary copy of the object.  With large objects, that wastes time.
Actually, I recommend changing it to:

ostream& operator<<(ostream& s, const matrix& m)
                                ^^^^^

This prevent you from accidently changing m within the function.  Whenever
your function shouldn't change the value of its argument, make the argument
a const &.  Whenever the function shouldn't change the value of the calling
object (*this), make the function const, if your version of C++ supports
that. 

===========================================================================
Josh Mittleman (mittle@ibm.com or joshua@paul.rutgers.edu)
J2-C28 T.J. Watson Research Center, PO Box 704, Yorktown Heights, NY  10598

mvm@jedi.harris-atd.com (Matt Mahoney) (03/15/91)

In article <1991Mar13.170912.4020@cs.UAlberta.CA> dwelly@saddle-lk.cs.UAlberta.CA (Andrew Dwelly) writes:
>
>Would someone take pity on me and tell me what I'm doing wrong ???
>
>class matrix
>{
>    float **m;
>    int   rows;
>    int   cols;
>public:
>
>            matrix(int,int);
>            ~matrix();
>    float*& operator[](int);
>    int     sizer();
>    int     sizec();
>    friend matrix 
>            operator*(matrix&, matrix&);
>};
>
  code deleted ...

The * function has to copy a matrix when it returns, but there is
no copy constructor.  The default copy constructor just copies the
contents, including pointers.  The segmentation fault probably occurs
when the destructors for the original (inside operator*) and the 
copy (cout << m * n) delete the same memory.  To fix this, add:

	matrix(const matrix&);  // Copy constructor
	matrix& operator=(const matrix&);  // Assignment

to your class.  Both functions should allocate new memory when they copy
their arguments.

Also, you may want to change operator* to:

	friend matrix operator*(const matrix&, const matrix&);

so you can pass temporary expressions, as in a * b * c.

-----------------------------------------
Matt Mahoney, 407-727-5431, mvm@epg.harris.com
#include <disclaimer.h>