Skip to content

Friend_functions

Friend functions and operator overloading are powerful C++ features that allow you to extend the language’s functionality for user-defined types. This chapter covers how to use them effectively.

A friend function has access to private and protected members of a class, even though it’s not a member of that class.

class Vector {
private:
double x, y;
public:
Vector(double xVal, double yVal) : x(xVal), y(yVal) {}
// Declare function as friend
friend double magnitude(const Vector& v);
};
// Define friend function (not a member!)
double magnitude(const Vector& v) {
// Can access private members directly
return std::sqrt(v.x * v.x + v.y * v.y);
}
int main() {
Vector v(3, 4);
std::cout << "Magnitude: " << magnitude(v) << std::endl; // 5
}

A friend class can access private members:

class Writer {
private:
std::string name;
public:
Writer(const std::string& n) : name(n) {}
friend class Document; // Document can access Writer's private
};
class Document {
private:
Writer author;
std::string content;
public:
Document(const Writer& w) : author(w) {}
void write(const std::string& text) {
content += text;
// Can access Writer's private name!
std::cout << author.name << " wrote: " << text << "\n";
}
};

Warning: Use friend classes sparingly - they break encapsulation.

C++ allows you to redefine operators for user-defined types.

┌─────────────────────────────────────────────────────────────┐
│ Overloadable Operators │
├─────────────────────────────────────────────────────────────┤
│ Arithmetic → + - * / % ++ -- │
│ Comparison → == != < > <= >= │
│ Logical → && || ! │
│ Bitwise → & | ^ ~ << >> │
│ Assignment → = += -= *= /= %= <<= >>= &= |= │
│ Member Access → () [] -> │
│ Other → new delete , │
└─────────────────────────────────────────────────────────────┘
class Complex {
private:
double real, imag;
public:
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
// Overload + operator
Complex operator+(const Complex& other) const {
return Complex(real + other.real, imag + other.imag);
}
// Overload - operator
Complex operator-(const Complex& other) const {
return Complex(real - other.real, imag - other.imag);
}
void display() const {
std::cout << real << " + " << imag << "i\n";
}
};
int main() {
Complex c1(3, 4), c2(1, 2);
Complex c3 = c1 + c2; // Uses operator+
Complex c4 = c1 - c2; // Uses operator-
c3.display(); // 4 + 6i
}
class Complex {
private:
double real, imag;
public:
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
// Declare as friend
friend Complex operator+(const Complex& a, const Complex& b);
void display() const {
std::cout << real << " + " << imag << "i\n";
}
};
// Define as friend (not a member)
Complex operator+(const Complex& a, const Complex& b) {
return Complex(a.real + b.real, a.imag + b.imag);
}
class Point {
private:
double x, y;
public:
Point(double xVal = 0, double yVal = 0) : x(xVal), y(yVal) {}
// Friend for output stream
friend std::ostream& operator<<(std::ostream& os, const Point& p) {
return os << "(" << p.x << ", " << p.y << ")";
}
// Friend for input stream
friend std::istream& operator>>(std::istream& is, Point& p) {
return is >> p.x >> p.y;
}
};
int main() {
Point p1(3, 4), p2;
std::cout << p1 << "\n"; // (3, 4)
std::cin >> p2; // Enter: 5 6
std::cout << p2; // (5, 6)
}
class Rectangle {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() const { return width * height; }
// All comparison operators
bool operator==(const Rectangle& other) const {
return area() == other.area();
}
bool operator!=(const Rectangle& other) const {
return !(*this == other);
}
bool operator<(const Rectangle& other) const {
return area() < other.area();
}
bool operator>(const Rectangle& other) const {
return other < *this;
}
bool operator<=(const Rectangle& other) const {
return !(other < *this);
}
bool operator>=(const Rectangle& other) const {
return !(*this < other);
}
};
class Counter {
private:
int value;
public:
Counter(int v = 0) : value(v) {}
// Prefix ++
Counter& operator++() {
++value;
return *this;
}
// Postfix ++ (int parameter distinguishes it)
Counter operator++(int) {
Counter temp = *this;
value++;
return temp;
}
// Prefix --
Counter& operator--() {
--value;
return *this;
}
// Postfix --
Counter operator--(int) {
Counter temp = *this;
value--;
return temp;
}
int getValue() const { return value; }
};
int main() {
Counter c(5);
std::cout << ++c << "\n"; // 6 (prefix)
std::cout << c++ << "\n"; // 6, then c becomes 7
std::cout << c << "\n"; // 7
}
class Array {
private:
int data[10];
public:
Array() {
for (int i = 0; i < 10; i++) data[i] = 0;
}
// Non-const version for modification
int& operator[](int index) {
return data[index];
}
// Const version for read-only access
const int& operator[](int index) const {
return data[index];
}
};
int main() {
Array arr;
arr[0] = 100; // Write
std::cout << arr[0]; // Read
}
class Multiply {
public:
int operator()(int a, int b) const {
return a * b;
}
};
int main() {
Multiply mult;
std::cout << mult(5, 3); // 15 - looks like function call
}
#include <iostream>
#include <ostream>
#include <istream>
#include <cmath>
class Complex {
private:
double real;
double imag;
public:
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
// Getters
double getReal() const { return real; }
double getImag() const { return imag; }
// Arithmetic operators
Complex operator+(const Complex& other) const {
return Complex(real + other.real, imag + other.imag);
}
Complex operator-(const Complex& other) const {
return Complex(real - other.real, imag - other.imag);
}
Complex operator*(const Complex& other) const {
return Complex(
real * other.real - imag * other.imag,
real * other.imag + imag * other.real
);
}
Complex operator/(const Complex& other) const {
double denominator = other.real * other.real + other.imag * other.imag;
return Complex(
(real * other.real + imag * other.imag) / denominator,
(imag * other.real - real * other.imag) / denominator
);
}
// Compound assignment
Complex& operator+=(const Complex& other) {
real += other.real;
imag += other.imag;
return *this;
}
// Unary operators
Complex operator-() const {
return Complex(-real, -imag);
}
// Comparison
bool operator==(const Complex& other) const {
return real == other.real && imag == other.imag;
}
bool operator!=(const Complex& other) const {
return !(*this == other);
}
// Stream operators
friend std::ostream& operator<<(std::ostream& os, const Complex& c);
friend std::istream& operator>>(std::istream& is, Complex& c);
// Magnitude
double magnitude() const {
return std::sqrt(real * real + imag * imag);
}
};
std::ostream& operator<<(std::ostream& os, const Complex& c) {
os << c.real;
if (c.imag >= 0) os << "+";
os << c.imag << "i";
return os;
}
std::istream& operator>>(std::istream& is, Complex& c) {
is >> c.real >> c.imag;
return is;
}
int main() {
Complex a(3, 4);
Complex b(1, 2);
std::cout << "a = " << a << "\n";
std::cout << "b = " << b << "\n";
Complex c = a + b;
std::cout << "a + b = " << c << "\n";
Complex d = a * b;
std::cout << "a * b = " << d << "\n";
std::cout << "|a| = " << a.magnitude() << "\n";
return 0;
}
  1. Prefer member functions when the left operand is your type
  2. Use friend functions when you need access to private members
  3. Keep operators consistent with their built-in meaning
  4. Don’t overload unrelated operators
  5. Consider return types - return by value for rvalues, by reference for lvalues
  • Friend functions have access to private members without being members
  • Use friend functions sparingly as they break encapsulation
  • Operator overloading allows custom types to use operators
  • Stream operators (<<, >>) enable I/O for custom types
  • Keep operator semantics consistent with their built-in meaning

Now let’s learn about the Standard Template Library (STL).

Next Chapter: 03_stl/14_stl_containers.md - STL Containers Overview