Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Canonical operator overloading?

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;
}
like image 358
Eclipse Avatar asked Jan 07 '09 23:01

Eclipse


4 Answers

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.

like image 168
John Mulder Avatar answered Oct 13 '22 01:10

John Mulder


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);
  e + 4; // okay: right operand is implicitly converted to example
  e + f; // okay: no conversions needed.
  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).

like image 24
coppro Avatar answered Oct 12 '22 23:10

coppro


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.

like image 29
wilhelmtell Avatar answered Oct 13 '22 00:10

wilhelmtell


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.

like image 24
Martin York Avatar answered Oct 12 '22 23:10

Martin York