Matrix operations

E

et al.

Hi! I am learning C++, and I am trying to understand how operators
work, and should work. So, I've decided to do yet another matrix class!
Anyway, I'm stuck with deciding how I should implement things here,
mainly regarding operator overloading.

What I've done so far is this:

class cmatrix
{
public:
explicit cmatrix(unsigned int rows, unsigned int cols);
explicit cmatrix(cmatrix& src);
virtual ~cmatrix();
virtual double& at(unsigned int r, unsigned int c);
virtual cmatrix& operator=(const cmatrix src);
private:
vector<unsigned int, double> storage;
};

and I've successfully implemented the operator= (or better: it works),
but now I've doubts with other operations! Before that, is my copy
constructor well defined? And a silly question... does the operator=
need a reference, or is it ok to leave it as I wrote?

My question is easy: how do I multiply two matrices? I mean, in the
"definition" sense, this is not an algorithmic doubt! Let's suppose I
want to write this:

M = N * Q;

with M, N, and Q being cmatrix instances. That is the equivalent,
according to Stroustrup's book, of

M.operator=(N.operator*(Q));

The problem is... How should I declare the operator* ? I don't want to
modify N, and I'd like to avoid pointers! Forgive this silly question,
but I'm still learning! :)

Another problem is with cvector, derived from cmatrix. Will the same
operator work? Like:

v = M * w;

or worse, what if I want to overwrite a cvector or cmatrix?

v = M * v;


Thanks for any help you can give me!
 
V

Victor Bazarov

Hi! I am learning C++, and I am trying to understand how operators work,
and should work. So, I've decided to do yet another matrix class!
Anyway, I'm stuck with deciding how I should implement things here,
mainly regarding operator overloading.

What I've done so far is this:

class cmatrix
{
public:
explicit cmatrix(unsigned int rows, unsigned int cols);
explicit cmatrix(cmatrix& src);

A copy c-tor does not have to be explicit. I am not even sure what it
means for a copy c-tor to be explicit. Also, the *usual* copy c-tor has
the reference to const as its argument:

cmatrix(cmatrix const& src);

(also why did you call it "cmatrix", why not just "matrix"? any
relation to the C language?)
virtual ~cmatrix();
virtual double& at(unsigned int r, unsigned int c);
virtual cmatrix& operator=(const cmatrix src);

Better to pass the argument by reference, and why is it virtual?

cmatrix& operator=(cmatrix const& src);
private:
vector<unsigned int, double> storage;
};

and I've successfully implemented the operator= (or better: it works),
but now I've doubts with other operations! Before that, is my copy
constructor well defined? And a silly question... does the operator=
need a reference, or is it ok to leave it as I wrote?

See above.
My question is easy: how do I multiply two matrices? I mean, in the
"definition" sense, this is not an algorithmic doubt! Let's suppose I
want to write this:

M = N * Q;

with M, N, and Q being cmatrix instances. That is the equivalent,
according to Stroustrup's book, of

M.operator=(N.operator*(Q));

The problem is... How should I declare the operator* ? I don't want to
modify N, and I'd like to avoid pointers! Forgive this silly question,
but I'm still learning! :)

You're doing fine. If you decide to make the multiplication operator a
member, then you do

matrix operator*(const matrix& other) const;

since it doesn't modify either operand. You could also define it a
non-member, and make it a friend:

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

Notice that the arithmetic operand will create an object and return it
by value.
Another problem is with cvector, derived from cmatrix. Will the same
operator work? Like:

v = M * w;

Not sure what you mean. What's a cvector?
or worse, what if I want to overwrite a cvector or cmatrix?

v = M * v;

That should not be a problem if you define the function correctly.
Return a value from it. The call to operator= will copy the returned
object into the second (right-hand side) operand *after* the value of
that operand were used to calculate the return value of the operator*.
Thanks for any help you can give me!

You're welcome, and ask more!

V
 
J

Jonathan Lee

Anyway, I'm stuck with deciding how I should implement things here,
mainly regarding operator overloading.

Some general advice first: google for "operator overloading"
and check out the Parashift link that comes up, and probably
wikibooks. Also, there have been a number of threads in this
newsgroup about good books to start out with. I'd probably
look for that, too.

Before that, is my copy constructor well defined?

In C++ speak you haven't defined it, here. Just declared
it. Anyway, the usual declaration would be

cmatrix(const cmatrix&);

i.e., drop the explicit and add const. The const will
allow you to copy from a wider range of sources. The
explicit is probably not necessary unless you have some
specific reason to do so.
And a silly question... does the operator=
need a reference, or is it ok to leave it as I wrote?

It doesn't, but it's (more?) common to write it as
accepting a reference. If you're going to pass by value
as you've done, you don't need const. The argument is
your own private copy that exists for the function call
only. You can do what you want to it.
The problem is... How should I declare the operator* ?

Inside the class:

cmatrix operator*(const cmatrix& Q) const;

Or you have the option of doing a global overload outside
the class

cmatrix operator*(const cmatrix& N, const cmatrix& Q);

Quite often these operators call their assignment
equivalents. For example

cmatrix operator*(const cmatrix& N, const cmatrix& Q) {
cmatrix M(N); // make a copy
M *= Q; // call the mul-and-assign operator
return M;
}
Another problem is with cvector, derived from cmatrix. Will the same
operator work? Like:

v = M * w;

You're going to need to write a separate assignment operator,
but sure.
or worse, what if I want to overwrite a cvector or cmatrix?

v = M * v;

The result of M * v is a temporary, which is then used for
the assignment. The temp doesn't get a name, but it's a
distinct object from v and so "overwriting" does not occur.

Note that you *can* overwrite in this sense

v = v;

But usually in operator=(...) you just check that the source
is not the same as the *this object before making a useless
copy.

--Jonathan
 
J

James Kanze

On Jul 23, 8:45 am, et al. <[email protected]> wrote:

[...]
In C++ speak you haven't defined it, here. Just declared
it. Anyway, the usual declaration would be
cmatrix(const cmatrix&);
i.e., drop the explicit and add const. The const will
allow you to copy from a wider range of sources. The
explicit is probably not necessary unless you have some
specific reason to do so.

If he uses the classical patterns, with std::vector for the
data, and a standard integral types for the dimensions, he
doesn't necessarily have to declare a copy constructor; the one
the compiler provides will have the correct semantics. (He may
still want to define one, in order to be able to instrument it,
or simply to prevent it from being inline.)

[...]
Inside the class:
cmatrix operator*(const cmatrix& Q) const;
Or you have the option of doing a global overload outside
the class
cmatrix operator*(const cmatrix& N, const cmatrix& Q);

A production level Matrix class would almost certainly not
return a Matrix, but some sort of proxy, for performance
reasons.

For learning, however, this is the first pattern you should
learn, and it works well for many, many types.
Quite often these operators call their assignment
equivalents. For example
cmatrix operator*(const cmatrix& N, const cmatrix& Q) {
cmatrix M(N); // make a copy
M *= Q; // call the mul-and-assign operator
return M;
}

If this pattern is being used, then typically, you don't write
the operator* at all, but rather derive, e.g.:

class Matrix : public ArithmeticOperators<Matrix>
{
// ...
};

The template class ArithmeticOperators takes care of all of the
binary operators, provided you define the <op>= function (which
traditionally will be a member, although there are good
arguments that this should not be the case).
You're going to need to write a separate assignment operator,
but sure.

Without seeing exactly what he means, it's hard to say. I don't
quite see how Vector could derive from Matrix.
The result of M * v is a temporary, which is then used for
the assignment.

Which is why operator* shouldn't return a Matrix in production
code.
The temp doesn't get a name, but it's a distinct object from
v and so "overwriting" does not occur.
Note that you *can* overwrite in this sense
But usually in operator=(...) you just check that the source
is not the same as the *this object before making a useless
copy.

Given the frequency of self-assignment, adding the check may
globally slow things down: you save a lot of time in
self-assignment, but you slow every assignment down by the time
it takes to make the check.
 
J

Jonathan Lee

If this pattern is being used, then typically, you don't write
the operator* at all, but rather derive, e.g.:

    class Matrix : public ArithmeticOperators<Matrix>
    {
        //  ...
    };

Where is this class from? Or is it just something of your
invention? In any case, I guess it's an interesting idea.
But since the OP is asking about how to do operator
overloading, I would stand by my recommendation to do them
"manually".

And really.. how often do you define a new arithmetic
type?
Without seeing exactly what he means, it's hard to say.  I don't
quite see how Vector could derive from Matrix.

As a 1-column or 1-row Matrix, I expect. Mathematically, there
isn't much distinction between a matrix and a vector as a special
case of a matrix. In particular, if he's doing any linear algebra,
setting things up this way seems reasonable.
Given the frequency of self-assignment, adding the check may
globally slow things down: you save a lot of time in
self-assignment, but you slow every assignment down by the time
it takes to make the check.

You're right, of course, but it also seems like a micro-optimization.
I use this all the time and neither gprof nor Valgrind has ever
scolded me for it.

--Jonathan
 
J

James Kanze

Where is this class from? Or is it just something of your
invention?

This particular version is my own, but I imagine all experienced
C++ programmers have something similar in their tool bag.
In any case, I guess it's an interesting idea.
But since the OP is asking about how to do operator
overloading, I would stand by my recommendation to do them
"manually".

Certainly. I was commenting on your proposal as to the pattern.
If you use the pattern of defining the <op>= operator first
(which you almost certainly wouldn't in a production matrix
class---this is an exercise), then you'd probably want to use
such a base class for the <op> operators. It's worth
mentionning the idiom, at the least, even if the poster needs to
know how to write the <op> operator without the base class
template, if only in order to be able to implement the base
class template.

(This is different than the question of how to write a
production quality matrix class---writing a production quality
matrix class needs mastery of a number of concepts which the
poster probably hasn't seen yet.)
And really.. how often do you define a new arithmetic
type?

It depends on the application domain; there's quite a few in my
current work. But even if you don't define new arithmetic
types, it's not rare to define a type which supports some
arithmetic operators.
As a 1-column or 1-row Matrix, I expect.

Which leads to the classical "is a cercle an ellipse" problem?
A vector has an additional invariant, compared to matrix.
Mathematically, there isn't much distinction between a matrix
and a vector as a special case of a matrix.

Yes, but the mathematical relationships don't always model by
inheritance---again, consider the "is a cercle an ellipse"
problem.
In particular, if he's doing any linear algebra,
setting things up this way seems reasonable.
You're right, of course, but it also seems like a micro-optimization.
I use this all the time and neither gprof nor Valgrind has ever
scolded me for it.

It certainly doesn't make the program incorrect, if it were
otherwise correct. Practically speaking (and matrix may be an
exception to this general rule), if you think you need a check
for self alignment, the code is probably incorrect.
 
J

Jonathan Lee

It's worth mentionning the idiom, at the least, even if
the poster needs to know how to write the <op> operator
without the base class template, if only in order to be
able to implement the base class template.

That's true.
It certainly doesn't make the program incorrect, if it were
otherwise correct.  Practically speaking (and matrix may be an
exception to this general rule), if you think you need a check
for self alignment, the code is probably incorrect.

No, of course it it doesn't make the code incorrect. I only mean
to comment on the way you presented it as an optimization. Sure,
it's an optimization. But it's one that is wayyyyyy down the list
of things I'll optimize.

On the other hand, to the OP (if you're still reading): you will
probably want to look up what self reference is, and how to avoid
the troubles it can cause. James is right about not having to
punish yourself with an explicit check.

--Jonathan
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top