views:

218

answers:

2

I, like so many programmers before me, am tearing my hair out writing the right-of-passage-matrix-class-in-C++. I have never done very serious operator overloading and this is causing issues. Essentially, by stepping through

This is what I call to cause the problems.

    cMatrix Kev = CT::cMatrix::GetUnitMatrix(4, true);
    Kev *= 4.0f;
    cMatrix Baz = Kev;
    Kev = Kev+Baz;  //HERE!

What seems to be happening according to the debugger is that Kev and Baz are added but then the value is lost and when it comes to reassigning to Kev, the memory is just its default dodgy values. How do I overload my operators to allow for this statement? My (stripped down) code is below.

//header
class cMatrix
{
private:
    float* _internal;
    UInt32 _r;
    UInt32 _c;
    bool _zeroindexed;

    //fast, assumes zero index, no safety checks
    float cMatrix::_getelement(UInt32 r, UInt32 c)
    {
        return _internal[(r*this->_c)+c];
    }

    void cMatrix::_setelement(UInt32 r, UInt32 c, float Value)
    {
        _internal[(r*this->_c)+c] = Value;
    }

public:
    cMatrix(UInt32 r, UInt32 c, bool IsZeroIndexed);
    cMatrix( cMatrix& m);
    ~cMatrix(void);

    //operators
    cMatrix& operator + (cMatrix m);
    cMatrix& operator += (cMatrix m);
    cMatrix& operator = (const cMatrix &m);
};

//stripped source file
cMatrix::cMatrix(cMatrix& m)
{
    _r = m._r;
    _c = m._c;
    _zeroindexed = m._zeroindexed;
    _internal = new float[_r*_c];

    UInt32 size = GetElementCount();

    for (UInt32 i = 0; i < size; i++)
    {
        _internal[i] = m._internal[i];
    }
}

cMatrix::~cMatrix(void)
{
    delete[] _internal;
}
cMatrix& cMatrix::operator+(cMatrix m) 
{
    return cMatrix(*this) += m;
}

cMatrix& cMatrix::operator*(float f) 
{
    return cMatrix(*this) *= f;
}

cMatrix& cMatrix::operator*=(float f) 
{
    UInt32 size = GetElementCount();

    for (UInt32 i = 0; i < size; i++)
    {
        _internal[i] *= f;
    }

    return *this;
}

cMatrix& cMatrix::operator+=(cMatrix m) 
{
    if (_c != m._c || _r != m._r)
    {
        throw new cCTException("Cannot add two matrix classes of different sizes.");
    }
    if (!(_zeroindexed && m._zeroindexed))
    {
        throw new cCTException("Zero-Indexed mismatch.");
    }

    for (UInt32 row = 0; row < _r; row++)
    {
        for (UInt32 column = 0; column < _c; column++)
        {
            float Current = _getelement(row, column) + m._getelement(row, column);
            _setelement(row, column, Current);
        }
    }

    return *this;
}

cMatrix& cMatrix::operator=(const cMatrix &m) 
{
    if (this != &m) 
    {
        _r = m._r;
        _c = m._c;
        _zeroindexed = m._zeroindexed;

        delete[] _internal;

        _internal = new float[_r*_c];

        UInt32 size = GetElementCount();

        for (UInt32 i = 0; i < size; i++)
        {
            _internal[i] = m._internal[i];
        }
    }
    return *this;
  }
+10  A: 

Your operators + and * must return by value, not by reference. You're returning a temporary variable by reference. Also, you're arguments are passed by value when it should be a const reference:

cMatrix cMatrix::operator+(cMatrix const& m) 
{
    cMatrix matrix(*this);
    matrix += m;
    return matrix;
}

cMatrix cMatrix::operator*(float f) 
{
    cMatrix matrix(*this);
    matrix *= m;
    return matrix;
}

You should take a look at Boost.Operators. This would let you implement only operator*= and operator+= and automatically provide correct implementations for operator+ and operator*.

PS: If you implement your matrix class just for the learning experience, don't hesitate to look at other implementations like the Matrix Template Library.

PPS: If you don't want to use boost, or if you just want to understand the best practice, take a look at Boost.Operator and do what they do.

Sebastian
My aim at this stage is to stick with the C++ std lib and avoid linking any othe libs if possible. Boost is handy but also a bit of a beast!
fneep
I would rather implement `operator+` as a non-member. Among other advantages, this also allows a C++11 compiler to optimize away the copy of an rvalue left argument if you pass it per value. (See my answer.)
sbi
@fneep: I understand that but you should still take a look at Boost.Operator to understand the canonical way to implement operators.
Sebastian
+8  A: 

IMO the canonical form of overloading addition is this:

class X {
public:
  X& operator+=(const X& rhs) { /*add rhs to *this*/ }
};

inline X operator+(X lhs, const X& rhs) {lhs+=rhs; return lhs;}

The same goes for -, *, /, where applicable.

Note that + returns a copy, not a reference. That's important, because A+B creates a new value, so it cannot return a reference to an existing one.
Also, it is a free function. IMO it's best to implement those of the binary operators which can be implement either as a member or as a free function as free functions, if they treat their operands symmetrically (as does +), and as member functions, if they treat their operands asymmetrically (as +=, which changes its left argument. If you implement operator+ as a member, you will have to make the function const (X operator+(const X& rhs) const), so that it can be invoked for constant elements on the left side.

sbi
Which is exactly what Boost.Operator would do.
Sebastian
@Sebastion: Yes, but IMO you should learn how to do this first, before taking shortcuts using boost. Otherwise, what are you going to do if you do something wrong with `+=` and the compiler spits a horrible error message into your face pointing deep into boost's bellows? This is a _right-of-passage implementation_, after all.
sbi
@sbi: And I think a good way to learn is to check out a canonical implementation.
Sebastian