Friend Functions and Friend Classes in C++
Table of Contents
- What is a Friend Function
- What is a Friend Class
- Friend Functions and Encapsulation
- Why Friend Functions Cannot Be Const
- Friend Functions and Inheritance
- Accessing Static Private Members
- Scope of Friend Functions
- Useful Cases for Friend Functions
What is a Friend Function
A friend function is a function that is granted access to the private and protected members of a class, even though it is not a member of that class. It is declared inside the class using the friend keyword but defined outside the class scope.
Global Function as Friend
A global function can be declared as a friend to access private members of a class.
#include <iostream>
using namespace std;
class Box {
private:
int width;
public:
Box(int w) : width(w) {}
// Declare global function as friend
friend void displayWidth(Box b);
};
// Define the friend function
void displayWidth(Box b) {
// Can access private member 'width'
cout << "Width of box: " << b.width << endl;
}
int main() {
Box box(10);
displayWidth(box); // Output: Width of box: 10
return 0;
}
Friend Function Inside Class
You can define a friend function directly inside the class body. The function is still not a member function, but it’s defined inline within the class. It can be called from outside using argument-dependent lookup (ADL).
#include <iostream>
using namespace std;
class Box {
private:
int width;
int height;
public:
Box(int w, int h) : width(w), height(h) {}
// Friend function defined inside the class
friend void displayBox(Box b) {
// Can access private members
cout << "Box dimensions: " << b.width << " x " << b.height << endl;
}
// Another friend function defined inside
friend void compareBoxes(Box b1, Box b2) {
cout << "Box1 area: " << (b1.width * b1.height) << endl;
cout << "Box2 area: " << (b2.width * b2.height) << endl;
if (b1.width * b1.height > b2.width * b2.height)
cout << "Box1 is larger" << endl;
else
cout << "Box2 is larger" << endl;
}
};
int main() {
Box box1(10, 20);
Box box2(15, 15);
// Call friend functions - they are NOT member functions
// so we don't use box1.displayBox()
displayBox(box1); // Output: Box dimensions: 10 x 20
compareBoxes(box1, box2); // Compares both boxes
return 0;
}
Important Notes:
- Even though defined inside the class, these are not member functions
- They are called directly by name, not through an object (e.g.,
displayBox(box1)notbox1.displayBox()) - They don’t have a
thispointer - They are found via argument-dependent lookup (ADL) when called
What is a Friend Class
A friend class is a class whose all member functions have access to the private and protected members of another class. It is declared using the friend keyword.
Entire Class as Friend
When an entire class is declared as a friend, all its member functions can access private members.
Important: Friendship is not mutual. If class A is a friend of class B, it doesn’t mean B is automatically a friend of A.
#include <iostream>
using namespace std;
class Box {
private:
int width;
int height;
public:
Box(int w, int h) : width(w), height(h) {}
// Declare entire class as friend
friend class BoxPrinter;
};
class BoxPrinter {
private:
string printerName;
public:
BoxPrinter(string name) : printerName(name) {}
void printDimensions(Box b) {
// BoxPrinter can access Box's private members
cout << "Width: " << b.width << ", Height: " << b.height << endl;
}
void printArea(Box b) {
cout << "Area: " << (b.width * b.height) << endl;
}
};
// Box CANNOT access BoxPrinter's private members
void testBox(Box b, BoxPrinter printer) {
// cout << printer.printerName << endl; // Error: cannot access private member
}
int main() {
Box box(10, 20);
BoxPrinter printer("HP-Printer");
printer.printDimensions(box); // Output: Width: 10, Height: 20
printer.printArea(box); // Output: Area: 200
return 0;
}
Key Points about Friendship:
- Friendship is one-way: BoxPrinter can access Box’s private members, but Box cannot access BoxPrinter’s private members
- Friendship must be explicitly granted: If you want mutual access, both classes must declare each other as friends
- Friendship is not transitive: If A is a friend of B, and B is a friend of C, it doesn’t mean A is a friend of C
Only One Member Function as Friend
You can declare only specific member functions of a class as friends, rather than the entire class.
#include <iostream>
using namespace std;
class Box; // Forward declaration
class Analyzer {
public:
void analyzeBox(Box b); // Will be friend
void processBox(Box b); // Will NOT be friend
};
class Box {
private:
int width;
public:
Box(int w) : width(w) {}
// Only analyzeBox is friend, not processBox
friend void Analyzer::analyzeBox(Box b);
};
void Analyzer::analyzeBox(Box b) {
cout << "Analyzing box with width: " << b.width << endl; // Works
}
void Analyzer::processBox(Box b) {
// cout << b.width << endl; // Error: cannot access private member
cout << "Processing box..." << endl;
}
int main() {
Box box(15);
Analyzer analyzer;
analyzer.analyzeBox(box); // Output: Analyzing box with width: 15
analyzer.processBox(box); // Output: Processing box...
return 0;
}
Friend Functions and Encapsulation
While friend functions allow access to private and protected data members, which technically breaks encapsulation, they are still useful in certain scenarios:
-
Operator Overloading: When overloading binary operators (like
+,-,<<,>>) where the left operand is not a class object. -
Bridge Between Two Classes: When two tightly coupled classes need to share data efficiently without exposing it publicly.
-
Testing and Debugging: Unit tests may need access to internal state without making everything public.
-
Performance Optimization: Avoiding getter/setter overhead when frequent access is needed between closely related classes.
-
Legacy Code Integration: When integrating with existing code that requires direct access to internal structures.
-
Implementation of Certain Design Patterns: Patterns like Iterator or Visitor may benefit from friend access.
-
Symmetric Operations: When operations need to treat multiple objects equally (like comparing two objects).
Why Friend Functions Cannot Be Const
A friend function cannot be declared as const because:
-
Not a Member Function: The
constkeyword in a member function indicates that the function doesn’t modify the object it’s called on (the implicitthispointer points to a const object). -
No Implicit
thisPointer: Friend functions are not member functions, so they don’t have an implicitthispointer to qualify as const or non-const. -
Parameter-Based Qualification: If a friend function shouldn’t modify an object, you pass that object as a
constreference or pointer parameter.
class Box {
private:
int width;
public:
Box(int w) : width(w) {}
// Correct: Pass const reference if function shouldn't modify
friend void display(const Box& b);
// Incorrect syntax: friend functions can't be const
// friend void display(Box b) const; // Error!
};
void display(const Box& b) {
cout << b.width << endl;
// b.width = 10; // Error: cannot modify const object
}
Friend Functions and Inheritance
Friend functions are not inherited by derived classes. Friendship must be explicitly granted by each class.
#include <iostream>
using namespace std;
class Base {
private:
int baseData;
public:
Base(int d) : baseData(d) {}
friend void showBase(Base b);
};
class Derived : public Base {
private:
int derivedData;
public:
Derived(int b, int d) : Base(b), derivedData(d) {}
// showBase is NOT automatically a friend of Derived
};
void showBase(Base b) {
cout << "Base data: " << b.baseData << endl; // Works
}
void showDerived(Derived d) {
// cout << d.baseData << endl; // Error: cannot access
// cout << d.derivedData << endl; // Error: cannot access
}
int main() {
Base b(10);
Derived d(20, 30);
showBase(b); // Works
showBase(d); // Works (object slicing to Base)
return 0;
}
Key Point: If you want a friend function to access derived class members, you must explicitly declare it as a friend in the derived class as well.
Accessing Static Private Members
Friend functions can access static private data members just like instance members.
#include <iostream>
using namespace std;
class Counter {
private:
static int count;
int instanceId;
public:
Counter() {
instanceId = ++count;
}
friend void displayStatistics();
friend void displayInstance(Counter c);
};
// Initialize static member
int Counter::count = 0;
void displayStatistics() {
// Access static private member
cout << "Total objects created: " << Counter::count << endl;
}
void displayInstance(Counter c) {
// Access both static and instance private members
cout << "Instance ID: " << c.instanceId << endl;
cout << "Total count: " << Counter::count << endl;
}
int main() {
Counter c1, c2, c3;
displayStatistics(); // Output: Total objects created: 3
displayInstance(c2); // Output: Instance ID: 2, Total count: 3
return 0;
}
Scope of Friend Functions
The scope of a friend function depends on where it is defined:
-
Global Scope: If defined outside any class, it has global scope.
-
Namespace Scope: If defined within a namespace, it belongs to that namespace.
-
Not in Class Scope: Even though declared inside a class, friend functions are not members of that class and don’t belong to the class scope.
-
Access Rules: Friend functions can be called like any other function based on their actual scope, not through the class.
#include <iostream>
using namespace std;
namespace MyNamespace {
class Box {
private:
int width;
public:
Box(int w) : width(w) {}
friend void display(Box b); // Declared in class
};
// Defined in namespace scope
void display(Box b) {
cout << "Width: " << b.width << endl;
}
}
int main() {
MyNamespace::Box box(50);
// Call using namespace scope, not class scope
MyNamespace::display(box); // Correct
// box.display(); // Error: not a member function
return 0;
}
Important: Friend functions are called directly by their name (with appropriate namespace qualification if needed), not as member functions through an object.
Useful Cases for Friend Functions
Friend functions are particularly useful in the following scenarios:
1. Operator Overloading
Friend functions are commonly used for operator overloading, especially for binary operators and I/O stream operators where the left operand is not your class object.
Note: Operator overloading will be covered in detail in a separate chapter.
2. Implementing Bridge Between Tightly Coupled Classes
When two classes need to work together closely and share internal state.
class Engine;
class Car {
private:
string model;
public:
Car(string m) : model(m) {}
friend class Engine; // Engine can access Car's internals
};
class Engine {
private:
int horsepower;
public:
Engine(int hp) : horsepower(hp) {}
void diagnose(Car& car) {
cout << "Diagnosing " << car.model << " with "
<< horsepower << "hp engine" << endl;
}
};
3. Factory Functions
Friend functions can act as factory methods that construct objects with access to private constructors.
Note: Factory patterns will be covered in detail in a separate chapter.
4. Unit Testing
Friend functions and classes allow test code to access private members without making them public, enabling thorough testing while maintaining encapsulation in production code.
#include <iostream>
using namespace std;
class BankAccount {
private:
double balance;
string accountNumber;
public:
BankAccount(string accNum, double b) : accountNumber(accNum), balance(b) {}
void deposit(double amount) {
if (amount > 0)
balance += amount;
}
bool withdraw(double amount) {
if (amount > 0 && balance >= amount) {
balance -= amount;
return true;
}
return false;
}
#ifdef UNIT_TEST
friend class AccountTester;
#endif
};
#ifdef UNIT_TEST
class AccountTester {
public:
static void testBalance() {
BankAccount acc("ACC123", 1000.0);
// Direct access to private members for testing
cout << "Initial balance: " << acc.balance << endl;
acc.deposit(500);
cout << "After deposit: " << acc.balance << endl;
acc.withdraw(200);
cout << "After withdrawal: " << acc.balance << endl;
// Verify internal state
if (acc.balance == 1300.0)
cout << "Test PASSED!" << endl;
else
cout << "Test FAILED!" << endl;
}
};
#endif
int main() {
#ifdef UNIT_TEST
AccountTester::testBalance();
#else
BankAccount acc("ACC456", 2000.0);
acc.deposit(500);
acc.withdraw(300);
cout << "Production mode - private members protected" << endl;
#endif
return 0;
}
Benefits:
- Test code can verify internal state without exposing it publicly
- Conditional compilation keeps test access separate from production code
- Maintains encapsulation while enabling comprehensive testing
Summary
Friend functions and friend classes provide controlled access to private members when:
- Encapsulation needs can be met through careful design
- The relationship between classes is well-defined and stable
- Performance or design patterns require direct access
- Operator overloading or symmetric operations are needed
Use them judiciously to maintain good object-oriented design principles while solving practical problems that arise in real-world programming.