4
votes

I am learning c++ on my own. I was studying operator overloading, i was able to understand addition and subtraction operator overloading. But overloading of I/O operators is a bit confusing. I have created a class for Complex numbers, now i am overloading operators.

Function prototype from Complex.h

friend ostream& operator<<(ostream&, const Complex&);

Function from Complex.cpp

ostream& operator<<(ostream& os, const Complex& value){
os << "(" << value.r <<", "
<< value.i << ")" ;
return os;
}
  1. Can anyone explain (on a basic level) why we have to use a friend function declaration here?
  2. Why do we have to pass all arguments and the return type of the operator by reference?
  3. This function works fine without using const, but why are we using const here? What is the advantage of passing Complex as a constant reference?
6
You show 2 differents function, operator<< and Complex::operator/... Copy/Paste error? If not, specify the problem on each one.Holt
+1 question looks clear to me.fonZ
@Deduplicator I can't spot any answer there, directly referring to these 3 points in the OP's question. If so, it's pretty buried in there and just accessible via folloowing additional links.πάντα ῥεῖ
@πάνταῥεῖ: Right, it does not explain the reasons for those points. Retracted. Still, that should be read by the op anyway: Operator overloadingDeduplicator

6 Answers

3
votes

You do not have to make the streaming operator a friend. It does have to be externally declared as the Complex object is on the right-hand side of the operator.

However if your Complex class has a way to access the members required (possibly through getters) you could get the streaming operator to use them. Say:

std::ostream& operator<<( std::ostream& os, Complex const& cpx )
{
   return os << cpx.getReal() << ',' << cpx.getImaginary();
}

Your operator/ overload can be done as an internal function but actually is better implemented also as an external function with two const& parameters. If it is a member function it needs to be a const-member. Yours is not.

You might implement it based on operator /= thus

Complex operator/ ( Complex const& lhs, Complex const& rhs )
{
    Complex res( lhs );
    res /= rhs; // or just put return in front of this line
    return res; 
}
2
votes

For friend ostream& operator<<(ostream&,const Complex&); :

  1. Because you declare a free function here, and would like it to access the internals (private/protected) of your Complex objects. It is very common to have "friend free functions" for those overloads, but certainly not mandatory.

  2. Because streams are non copyable (it does not make sense, see also this post), passing by value would require a copy. Passing Complex by value would also require a useless copy.

  3. Because those output operators are not supposed to modify the objects they are working on (Input operators are, obviously), so add const to ensure that.

1
votes
  1. Can anyone explain (on a basic level) why we have to use a friend function declaration here?

    If you declare a class friend of another class, then you are saying that the friend class can access the private and protected attributes and functions of your class.
    In your case you declare ostream& operator<< as friend which means that within the body of that function the ostream class will be able to access private and protected functions and attributes of your complex class.

  2. Why do we have to pass all arguments and the return type of the operator by reference?

    Because it was written like that to avoid to make a copy of the ostream object. So your << overload will use the same object without making a copy each time you use it.

  3. This function works fine without using const, but why are we using const here? What is the advantage of passing Complex as a constant reference?

    const after the function means that you are not changing any attributes of the class, which you dont.

I think that is about right and i hope it helps, but if anyone wants to comment feel free.

0
votes

For non-friend member function operators, there is only one argument, the right-hand side of the operator. So for your example with the division operator you can do like this:

Complex a = ...;
Complex b = ...;

Complex c = a / b;

and the compiler will actually see the last as

Complex c = a.operator/(b);

This means that for e.g. the output operator, if it's a normal member function you "output" to an instance of the class. in other words it would be used like

Complex a = ...;
Complex b = ...;
a << b;  // Really a.operator<<(b);

Clearly this is not what's wanted when implementing an operator that should output to a stream. Therefore we need to use a non-member function, which can take two different arguments.


For the second point, remember that the default method of passing arguments is by value, which means that the values are copied. For smaller classes it doesn't matter, but if there's a lot of data, or the class has a complex copy-constructor, then that might incur quite a performance hit. Also, some types and classes can't be copied, for example std::istream so you have to pass them by reference.


For the last point, by making some arguments const you tell both the compiler and the user of the function that it will not change the argument. Besides making it easier for users of the function to know that calling the function will not have any side-effects, it will also allow the compiler to possibly do some optimizations.


Generally regarding operator overloading, you might want to read this reference on the subject.

0
votes

Can anyone explain why we have to use friend function here?

A friend of a class can access private or protected variables/functions from the class in which it is specified as a friend. It is necessary to make the I/O operators a friend of class Complex if they need to access any private functionality. If not, one may simply define the function outside of the class without declaring it as such.

Why do we have to pass operator by reference?

I'm not sure what you mean. The operator is a function we are defining, not a parameter that is being passed anywhere. Are you referring to the parameters that it takes? Well, std::ostream is taken as a reference type because taking it by value would cause a copy, something which stream classes do not allow. Try it and you will get a compiler error. I will touch on why Complex is taken as a reference type below.

This function works fine without using const, but why are we using const here? What is the advantage of passing Complex as a constant object?

First of all, why not use const? The const type modifier is a clear statement to the compiler that we will not change the object in any way. It also makes it clear to the maintainers of your code. Besides, writing out an object typically requires the value, meaning no modifications to the object are necessary.

Using a reference to const also allows you to pass rvalues or tempoaries to the function, which are not possible with a reference to non-const, which only take lvalues. For example:

struct S { };

void f(S&);
void h(S const&);

int main()
{
    S s;

    f(s); // OK
    f(S()); // ERROR!

    h(s); // OK
    h(S()); // OK
}
0
votes
friend ostream& operator<<(ostream&, const Complex&);

Here the function needs be friend because you may need to be able to access the private member of complex.

When you overload << for Complex class it is likely that you will need to access the data members of Complex class in the overloaded functions and the data members may be private. In case they are private, to access the data members in operator << you will need to make it a member of complex class which is not possible since the left side object of operator is of ostream class type so only option is to make it a friend of Complex and overlaod << using global scope function.

Now the object is returned by reference to avoid creation of multiple copy of the object as told by others. And the object has to be returned because it is going to support when the operator is using for cascaded write like `cout<

The argument is passed as a const because it in always a good practice to receive the object as const if it is not going to be changed in side the function. This makes it work on even const objects.