Click here to Skip to main content
15,885,216 members
Please Sign up or sign in to vote.
3.00/5 (1 vote)
See more:
As an example, consider the following code:


C++
class Rational{
private:
    int a;
    int b;
public:
    Rational(); // Definition doesn't matter as for now

/* Here's the deal: */
    Rational operator*(Rational& rhs); //The same as below but differs in returned value type
    Rational operator*(Rational rhs);
    Rational& operator*(Rational rhs); //This will throw an error of course
    Rational& operator*(Rational& rhs); // This will too.
    Rational* operator*(Rational& rhs); //And so on...
    Rational* operator*(Rational rhs); 
    //...

};


We redefine them four times so that we can make code like this:

C++
Rational r(4,5);
Rational r2(3,5);
Rational r3 = r*r2;

Rational * pr = r*r2;


So what's the design pattern in C++ or workaround or correct way to do things when this problem arises? What do normally programmers do?

Thanks.
Posted
Updated 14-Oct-13 3:55am
v2
Comments
Richard MacCutchan 14-Oct-13 10:06am    
You seem to be confusing return values with operator overloading.
[no name] 14-Oct-13 19:04pm    
I don't get the question. When I implement operator * and write x*y what has the client got to do with it? Don't try to reinvent the wheel. I agree with Philippe Mori. Maybe revisit the design.
Stefan_Lang 15-Oct-13 4:32am    
You seem to be confusing the topic title with the topic.

The "good" way to define operator, is to define them so that it match (as much as possible) what would happen with built-in types.

For binary * operator, usually you should do it this way:
C++
class Rationnal
{
public:
  Rational& operator*=(const Rational &rhs)
  {
    a *= rhs.a;
    b *= rhs.b;
  } 
  // Other stuff omitted...
};


And have a free function (might also be a friend) for binary * operator:
C++
inline Rational operator*(const Rational &lhs, const Rational & rhs)
{
  Rational result(lhs);
  result *= rhs;
  return result;
}

This is usually a good pattern to follows as you minimize code while being efficient. *= operator can be implemented more efficiently and then you can define binary * operator based on it.

Last function could also be written:
C++
inline Rational operator*(const Rational &lhs, const Rational & rhs)
{
  return Rational(lhs) *= rhs;
}

I don't remember if it would usually be more efficient or not.
 
Share this answer
 
v2
Thedre's some invalid code in your example, as well as bits that won't be useful:
C++
    Rational operator*(Rational& rhs); //The same as below but differs in returned value type
(1)    Rational operator*(Rational rhs);
(2)    Rational& operator*(Rational rhs); //This will throw an error of course
(3)    Rational& operator*(Rational& rhs); // This will too.
(4)    Rational* operator*(Rational& rhs); //And so on...
(5)    Rational* operator*(Rational rhs); 

(1) this would work, but you should always strive to pass functuion arguments as const reference, unless they are primitive types. In this case fix that signature this way:
C++
Rational operator*(const Rational& rhs);

(2) you can not define two function signatures that only differ by return type! This is a restriction of the C++ language. Besides, it is a bad idea to return a reference as a result from an operator with two operands: what does the return object refer to? First argument? Second Argument? Something else? If it's the latter, does it need clean-up, and who is responsible for that? Fix this by eliminating the function entirely.

(3) Again, doesn't make sense to return a reference (see 2), and again, the argument should be const reference (see 1). Also, changing an argument type from some type to reference-to-type is not sufficient for the purpose of function overloading! See below.

(4) same as above, only that it makes even less sense to return a pointer than a reference! What does it point to? Is it a heap object, and if so, who is responsible for releasing it?

(5) all of the above...

As pointed out in solution 2, it is a good rule of thumb to only define unary operators and arithmetical assignment operators right within the class, and define binary operators outside of the class. The former will should normally return a reference ( i. e. *this ), and the latter should simply return an object ( i. e. not a reference or pointer ).

An example, just for the variants of operator*:
C++
class Rational {
   ...
   Rational& operator*=(const Rational& rhs);
   Rational& operator*=(int rhs);
   friend Rational operator*(const Rational& lhs, const Rational& rhs);
   friend Rational operator*(const Rational& lhs, int rhs);
   friend Rational operator*(int lhs, const Rational& rhs);
   ...
};

Note that I defined the binary operators as friend functions, i. e. not as a member function of the class, although they are still declared within the class definition. It's also helpful to make them a friend because you probably need to access the private class members.

Also note that the last operator could not be defined as a class member function at all! This is a general problem with binary arithmetic operators, and one of the reasons why you need to define some of them outside the class. Another reason is that an operator may work on two arguments of different class type (e. g. multiply matrix by vector): in that case it is best to not define the operator as a member of function of either operand class.

A final word of advice: do look up articles on member function and operator overloading: they only work if the signatures are sufficiently different to give the compiler a chance to decide when to use which when you call them in your code. Given your overload definitions above and the following code, which of your overloads should be called, and how could the compiler decide which it should be?
C++
Rational a, b;
...
a*b;

1. Return type: it is valid to call a function without checking or assigning it's return type. In this example the compiler won't know what the return type of the operator should be, and it therefore must consider operators with all return types!
2. Argument types: should a or b be passed by value or by reference? If you define an operator for both variants, the compiler wouldn't know which to use in this example!
 
Share this answer
 
v2
the central consideration is to avoid overhead in constructing and copying data.

So the best practice is to use constant reference values for that, to also avoid bugs in manipulating the operands.

Rational& operator*(const Rational& rhs) const;//return a reference to a new object
 
Share this answer
 
Comments
Philippe Mori 14-Oct-13 16:15pm    
You never return a reference to a new object in usual implementation. Either a new object is returned by value or a reference is returned to this for operators that mofify the object itself (like operator *=).
Stefan_Lang 15-Oct-13 4:25am    
What Philippe said. Besides, what object would the reference refer to? Does it need clean-up? And if so, who is responsible to do it, and how is it done?

There are just too many problems you create when passing a reference as return type, it is rarely worth the trouble! Certainly not in operator overloading, where by common standards everyone expects that the return type is _not_ a reference, unless it is some kind of (arithmetical) assignment operator.

P.S.: Please note that if you create and pass an entirely new object as the result type, you must create that object on the heap, i. e. calling new. Doing so is much more expensive than creating the object locally on the stack and then copying it on return - so you're defeating the stated purpose.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900