Friendship is magic
Inside the body of a class, a friend declaration (using the friend
keyword) can be used to tell the compiler that some other class or function is now a friend. In C++, a friend is a class or function (member or non-member) that has been granted full access to the private or protected members of another class. In this way, a class can selectively give other classes or functions full access to their members without impacting anything else.
Friendship is always granted by the class whose members will be accessed (not by the class or function desiring access). Between access controls and granting friendship, a class always retains the ability to control who can access its members.
Friend non-member functions
A friend function is a function (member or non-member) that can access the private and protected members of a class as though it were a member of that class. In all other regards, a friend function is a normal function.
Let's take a look at an example:
#include <iostream>
class Accumulator
{
private:
int m_value { 0 };
public:
void add(int value) { m_value += value; }
// Here is the friend declaration that makes non-member function void print(const Accumulator& accumulator) a friend of Accumulator
friend void print(const Accumulator& accumulator);
};
void print(const Accumulator& accumulator)
{
// Because print() is a friend of Accumulator
// it can access the private members of Accumulator
std::cout << accumulator.m_value;
}
int main()
{
Accumulator acc{};
acc.add(5); // add 5 to the accumulator
print(acc); // call the print() non-member function
return 0;
}
In this example, we have declared a non-member function named print()
that takes an object of class Accumulator
. Because print()
is not a member of the Accumulator class, it would normally not be able to access private member m_value
. However, the Accumulator class has a friend declaration making print(const Accumulator& accumulator)
a friend.
Note that because print()
is a non-member function (does not have an implicit object), we must explicitly pass an Accumulator
object to print()
to work with.
Defining a friend non-member inside a class
Much like member functions can be defined inside a class if desired, friend non-member functions can also be defined inside a class. The following examples defines friend non-member function print()
inside the Accumulator
class:
#include <iostream>
class Accumulator
{
private:
int m_value { 0 };
public:
void add(int value) { m_value += value; }
// Friend functions defined inside a class are non-member functions
friend void print(const Accumulator& accumulator)
{
// Because print() is a friend of Accumulator
// it can access the private members of Accumulator
std::cout << accumulator.m_value;
}
};
int main()
{
Accumulator acc{};
acc.add(5); // add 5 to the accumulator
print(acc); // call the print() non-member function
return 0;
}
Although you might assume that because print()
is defined inside Accumulator
, that makes print()
a member of Accumulator
, this is not the case. Because print()
is defined as a friend, it is instead treated as a non-member function (as if it had been defined outside Accumulator
).
Syntactically preferring a friend non-member function
We prefer to use a non-member function over a member function.
#include <iostream>
class Value
{
private:
int m_value{};
public:
explicit Value(int v): m_value { v } { }
bool isEqualToMember(const Value& v) const;
friend bool isEqualToNonmember(const Value& v1, const Value& v2);
};
bool Value::isEqualToMember(const Value& v) const
{
return m_value == v.m_value;
}
bool isEqualToNonmember(const Value& v1, const Value& v2)
{
return v1.m_value == v2.m_value;
}
int main()
{
Value v1 { 5 };
Value v2 { 6 };
std::cout << v1.isEqualToMember(v2) << '\n';
std::cout << isEqualToNonmember(v1, v2) << '\n';
return 0;
}
In this example, we have defined two similar functions that check whether two Value
objects are equal. isEqualToMember()
is a member function, and isEqualToNonmember()
is a non-member function.
In isEqualToMember()
, we are passing one object implicitly and other explicitly. The implementation of the function reflects this, and we have to mentally reconcile that m_value
belongs to the implicit object whereas v.m_value
belongs to the explicit parameter.
In isEqualToNonmember()
, both objects are passed explicitly. This leads to better parallelism in the implementation of the function, as the m_value
member is always explicitly prefixed with an explicit parameter.