C++ Binary Operator Overloading & Friend Function
This is an episode of my C++ series. Imaging you are working on a project where you need to define a custom Pixel Struct/Class. How can we print a Pixel object?
Binary Operator Overloading
In C++, input and output from/to file, console … are in the form of bytes. Stream objects, cin
(std::istream), and cout
(std::ostream) help us handle these operations.
<<
is the operator for outputting to cout
. So if Pixel is a struct, by default its members are publicly available. So we can overload the <<
operator in this form:
#include <iostream>
struct Pixel{
unsigned int x;
unsigned int y;
};
std::ostream& operator<< (std::ostream& stream, const Pixel& p){
stream << "(" <<p.x << " " << p.y<<")";
// return the object to allow chaining
return stream;
}
Note:
<<
is a binary operator, i.e. operator that takes in two arguments. In a binary operator, the first argument is "the left operand", and for<<
, it's the output stream object, e.g.cout
. The second argument is thePixel
object- It’s quite common that we want to chain stream object together, like
cout << "a" << "b"
; Therefore, at the end of this function, we return the lvalue reference to the same stream object, so the following<<
actually operates on the same output stream object.
Friend Function
Now what if struct is a class, and its members are private? This is a typical use case for friend function. Declare the <<
overloading function as a friend, which gives <<
access to protected / private member
#include <iostream>
class Pixel{
private:
unsigned int x=1;
unsigned int y=2;
friend std::ostream& operator<< (std::ostream& stream, const Pixel& p);
};
std::ostream& operator<< (std::ostream& stream, const Pixel& p){
stream << "(" <<p.x << " " << p.y<<")";
// return the object to allow chaining
return stream;
}
Note:
- We declare the friend function in the private section. If its access identifier is public, that’s fine too. This is because a friend function doesn’t belong to the class, so it’s not a member of the class at all.
- As a general rule of thumb, don’t define too many friend functions, as that will break the encapsulation of the class. Function overloading however, is a typical usage of friend functions
“Friend Definition”
In the previous example, we declare a friendship within a class, but the function really is not a member of the class and therefore is defined outside. However, C++ does allow “friend definition”, that is, defining the friend function inside the class.
struct Pixel{
unsigned int x;
unsigned int y;
private:
friend std::ostream& operator<<(std::ostream& stream, const Pixel& p) {
stream << "(" << p.x << " " << p.y << ")";
return stream; <a name="#marker" id="marker"></a>
}
};
```
However, the << is still not considered a member of the class, and it can be applied to anywhere in the code base as long as Pixel is accessible. This is just a convenient way to “see things within one sight”
Fuzzy Scenario: ==
If we want to implement another binary operator ==
for the Pixel class, there are two ways to do this:
- As a non-member function following the above logic. Accessibility identifier does not matter:
class Pixel{
unsigned int x = 0;
unsigned int y = 0;
friend bool operator == (const Pixel& a, const Pixel& b);
};
bool operator == (const Pixel& a, const Pixel& b){
return a.x == b.x && b.y == b.y;
}
- Or as a member function since the left operand of
==
is thePixel
object itself.
class Pixel{
unsigned int x = 0;
unsigned int y = 0;
public:
bool operator == (const Pixel& other){
return other.x == x && other.y == y;
}
};
Note: since the overloading ==
function is a member, it needs to be public so it can be called outside. Also, when defining operator overloading functions, we only pass in the right operand as an argument. The left operand must be the object itself.