The simple approach is using a bit of wishful thinking: just think that you do have the two objects before writing the addition operation.
Really, what goes on is that when you are defining the operation you are writing an algorithm that will later be applied. At the place of call, when some user types a+b
, a
and b
will have to be valid instances of the type that you want to add. While defining the operation, the arguments to the function or method represent those to valid instances that will exist later.
Since this is homework, I will follow a different example. Consider writing a vector2d
class that contains two doubles representing x
and y
coordinates.
class vector2d {
public:
//...
private:
double x,y;
};
And you want to offer some operations that can be applied to vector2d
objects. You can define operator+=
as a way of storing in a vector the result of adding that same vector with a second one:
class vector2d {
public:
//...
vector2d& operator+=( const vector2d& rhs )
{
x += rhs.x;
y += rhs.y;
return *this;
}
};
For convention, we return a reference to a vector2d
from operator+=
. We operate both on the object that we are calling the operation (this is a member function, the *this
object) and on a second object that we take as parameter. Notice that at this point there is no single object created in the program, we are just defining the operation, not the operands. The right hand side argument rhs
is handled through a constant reference (we get a reference to the object that we promise not to change). The actual operation is simple, add each coordinate separately, and because the convention is returning a reference to the modified object, we return *this
.
We have defined how the operation is performed in terms of two objects, and now we can use it:
int main() {
vector2d a( 5, 10 ); // assume that there is a constructor that
// takes 2 doubles in the ellipsis above...
vector2d b( 2.5, 7.5 );
a += b;
}
Now, to be able to make the call, the user needs to have two objects. To define the operation we do not actually need instances of the objects, we abstract that by means of the parameters. But to actually use the operation we do need the objects on which to operate. At this point they are actually created and named a
and b
. Then the user calls the operation: a += b
. As it is a member method that call gets translated by the compiler into some less beautiful: a.operator+=(b)
, that is, it will call the member method operator+=
on the object a
passing b
as argument.
To provide a proper addition operation, we can write a free function. The addition does not apply to any of the two arguments, but rather creates a third argument so it kind of makes sense that it is not a member method.
vector2d operator+( vector2d lhs, const vector2d & rhs )
{
lhs += rhs;
return lhs;
}
This implementation is idiomatic (a common pattern). Once we have operatorX=
implemented we can implement operatorX
in terms of the previous. Now the trickery: we take the first argument by value. That way, the compiler makes a copy for us lhs
inside this function is not the first argument of the addition, but a copy of that. Then we use the existing operation to modify that local copy and we return the result to the user. The return object is also by-value.
The usage is similar:
int main() {
vector2d a( 5, 10 );
vector2d b( 2.5, 7.5 );
vector2d c = a + b;
}
At the place of call, the a+b
gets translated to operator+( a, b )
since it is a free function. Then because the first argument is passed by value, a copy of a
is made and used as lhs
in the function. The second argument is passed by reference and we promise not to change it (thus const). The result of the operation is stored in c
.