views:

546

answers:

4

Is there a canonical or recommended pattern for implementing arithmetic operator overloading in C++ number-like classes?

From the C++ FAQ, we have an exception-safe assignment operator that avoids most problems:

class NumberImpl;

class Number {
   NumberImpl *Impl;

   ...
};

Number& Number::operator=(const Number &rhs)
{
   NumberImpl* tmp = new NumberImpl(*rhs.Impl);
   delete Impl;
   Impl = tmp;
   return *this;
}

But for other operators (+, +=, etc..) very little advice is given other than to make them behave like the operators on built-in types.

Is there a standard way of defining these? This is what I've come up with - are there pitfalls I'm not seeing?

// Member operator
Number& Number::operator+= (const Number &rhs)
{
    Impl->Value += rhs.Impl->Value; // Obviously this is more complicated
    return *this;
}

// Non-member non-friend addition operator
Number operator+(Number lhs, const Number &rhs)
{
     return lhs += rhs;
}
+2  A: 

In Bjarne Stroustrup's book "The C++ Programming Language", in chapter 11 (the one devoted to Operator Overloading) he goes through witting a class for a complex number type (section 11.3).

One thing I do notice from that section is that he implements mixed type operations... this is probably expected for any numeric class.

In general, what you've got looks good.

John Mulder
Thanks for the pointer to the book
Eclipse
+3  A: 

The convention is to write operator+(const T&) and operator-(const T&) in terms of operator+=(const T&) and operator-=(const T&). If it makes sense to add and subtract to/from primitive types then you should write a constructor that constructs the object from the primitive type. Then the overloaded operators will also work for primitive types, because the compiler will call the appropriate constructor implicitly.

As you mentioned yourself, you should avoid giving access privileges to functions that don't need it. But in your code above for operator+(Number, const Number&) I'd personally make both parameters const references and use a temp. I think it isn't surprising the commenter below your question missed this; unless you have a good reason not to, avoid surprises and tricks and be as obvious as possible.

If you want your code to integrate with other numeric types, say std::complex, watch out for cyclic conversions. That is, don't supply operator OtherNumeric() in Numeric if OtherNumeric supplies a constructor that takes a Numeric parameter.

wilhelmtell
j_random_hacker
the object code will be the same, but it is just a matter of avoiding deviation from convention. you can live with this signature, but you might as well avoid a question by following the convention.
wilhelmtell
+3  A: 

It is traditional to write the operator X in terms of the operator =X
It is also traditional the all parameters to the standard operators are const

// Member operator
// This was OK
Number& Number::operator+= (Number const& rhs) 
{
    Impl->Value += rhs.Impl->Value; // Obviously this is more complicated
    return *this;
}

// Non-member non-friend addition operator
Number operator+(Number const& lhs,Number const& rhs)
{
     // This I would set the lhs side to const.
     // Make a copy into result.
     // Then use += add the rhs
     Number result(lhs);
     return result += rhs;
}

You mention assignment operator.
But you did not mention the copy constructor. Since your class has ownership of a RAW pointer I would expect you to define this as well. The Assignment operator is then traditionally written in terms of the copy constructor.

Martin York
j_random_hacker
@j_random_hacker: If you dont declare it const then you will be modifying the original value. x = a+b; I don't expect the value of 'a' to change.
Martin York
+2  A: 

The big thing to consider when writing any operator is that member operators do not undergo conversions on the left parameter:

struct example {
  example(int);
  example operator + (example);
};

void foo() {
  example e(3), f(6);
  4 + e; //okay
  e + f; //okay
  6 + e; //BAD: no matching call.
}

This is because conversion never applies to this for member functions, and this extends to operators. If the operator was instead example operator + (example, example) in the global namespace, it would compile (or if pass-by-const-ref was used).

As a result, symmetric operators like + and - are generally implemented as non-members, whereas the compound assignment operators like += and -= are implemented as members (they also change data, meaning they should be members). And, since you want to avoid code duplication, the symmetric operators can be implemented in terms of the compound assignment ones (as in your code example, although convention recommends making the temporary inside the function).

coppro
The second line of 'foo' should be 'e + 4' instead of '4 + e'.
Luc Touraille