The topic of Friend class is a topic that has generated great interest in today's society. With the advancement of technology and globalization, Friend class has become a relevant topic that impacts different aspects of daily life. From Friend class person or event that has marked a before and after, to Friend class concept or trend that is currently in vogue, Friend class is something that deserves our attention and analysis. In this article, we will explore the various aspects of Friend class and its relevance in modern society.
A friend class in C++ can access the private and protected members of the class in which it is declared as a friend.[1] A significant use of a friend class is for a part of a data structure, represented by a class, to provide access to the main class representing that data structure. The friend class mechanism allows to extend the storage and access to the parts, while retaining proper encapsulation as seen by the users of the data structure.
Similar to a friend class, a friend function is a function that is given access to the private and protected members of the class in which it is declared as a friend.
Since C++26, C++ supports "variadic friends" (i.e. friend classes that come from variadic templates).[2]
The following example demonstrates the use of a friend-class for a graph data structure, where the graph is represented by the main class Graph, and the graph's vertices are represented by the class Vertex.
import std;
template <typename T>
using SharedPtr = std::shared_ptr<T>;
using String = std::string;
template <typename T>
using UniquePtr = std::unique_ptr<T>;
template <typename T>
using HashSet = std::unordered_set<T>; // default-specialize the remaining template-parameters
class Vertex {
private:
// Vertex gives access-rights to Graph.
friend class Graph;
HashSet<SharedPtr<Vertex>> edges;
String name;
using EdgesIterator = decltype(edges.cbegin());
public:
explicit Vertex(const String& name):
name{std::move(name)} {}
EdgesIterator begin() const {
return edges.cbegin();
}
EdgesIterator end() const {
return edges.cend();
}
String getName() const {
return name;
}
};
class Graph {
private:
HashSet<SharedPtr<Vertex>> vertices;
using VerticesIterator = decltype(vertices.cbegin());
public:
~Graph() {
while (!vertices.empty()) {
VerticesIterator vertex = vertices.begin();
removeVertex(*vertex);
}
}
void addVertex(const String& name) {
UniquePtr<Vertex> vertex = std::make_unique<Vertex>(name);
VerticesIterator iter = vertices.insert(vertex.get());
}
void removeVertex(SharedPtr<Vertex> vertex) {
vertices.erase(vertex);
}
void addEdge(SharedPtr<Vertex> from, SharedPtr<Vertex> to) {
// Graph can access Vertex's private fields because Vertex declared Graph as
// a friend.
from->edges.insert(to);
}
VerticesIterator begin() const {
return vertices.cbegin();
}
VerticesIterator end() const {
return vertices.cend();
}
};
Using variadic friends:
import std;
template <typename... Friends>
class ClassWithFriends {
private:
int secretValue;
friend Friends...;
public:
explicit ClassWithFriends(int secret):
secretValue{secret} {}
};
class A {
public:
void readSecret(const class ClassWithFriends<A, B>& instance) const {
std::println("Inspecting secret value from A: {}", instance.secretValue);
}
};
class B {
public:
void readSecret(const class ClassWithFriends<A, B>& instance) const {
std::println("Inspecting secret value from B: {}", instance.secretValue);
}
};
int main(int argc, char* argv) {
ClassWithFriends<A, B> secretHolder(135);
A a;
B b;
a.readSecret(secretHolder);
b.readSecret(secretHolder);
}
A proper use of friend classes increases encapsulation, because it allows to extend the private access of a data-structure to its parts—which the data-structure owns—without allowing private access to any other external class. This way the data-structure stays protected against accidental attempts at breaking the invariants of the data-structure from outside.
It is important to notice that a class cannot give itself access to another class's private part; that would break encapsulation. Rather, a class gives access to its own private parts to another class—by declaring that class as a friend. In the graph example, Graph cannot declare itself a friend of Vertex. Rather, Vertex declares Graph a friend, and so provides Graph an access to its private fields.
The fact that a class chooses its own friends means that friendship is not symmetric in general. In the graph example, Vertex cannot access private fields of Graph, although Graph can access private fields of Vertex.
A similar, but not equivalent, language feature is given by C#'s internal access modifier keyword, which allows classes inside the same assembly to access the private parts of other classes. This corresponds to marking each class a friend of another in the same assembly; friend classes are more fine-grained.
Programming languages which lack support for friend classes, or a similar language feature, will have to implement workarounds to achieve a safe part-based interface to a data-structure. Examples of such workarounds are:
A is a friend of class B, class B is not automatically a friend of class A.A is a friend of class B, and class B is a friend of class C, class A is not automatically a friend of class C.Base is a friend of class X, subclass Derived is not automatically a friend of class X; and if class X is a friend of class Base, class X is not automatically a friend of subclass Derived. However, if class Y is a friend of subclass Derived, class Y will also have access to protected portions of class Base, just as subclass Derived does.