The friend keyword in C++ is often misunderstood. There's
a few good reasons to make use of friend in your C++ code,
but first let's go into what it means to be a friend, and why C++ needs
friends at all.
C++ classes have three levels of access to members; public,
protected, and private. Marking a function or
class as a friend allows them to access private and
protected members of another class.
For example, the following code would not compile, as
balance is a private member of
BankAccount:
class BankAccount {
public:
bool withdraw(float amount);
private:
float balance;
};
void remove_money(BankAccount &account) {
account.balance -= 100.0f;
}
So, how can you give remove_money() access to
balance? You add remove_money as a friend.
Similarly, you can also give another class the same level of access.
class BankAccount {
public:
friend void remove_money(BankAccount &account);
friend class BankAccountUnitTest;
...
There's a few common reasons that you might want to use
friendship to provide access into your classes.
Unit Testing
Implementing unit tests can be hard if you don't have
access to some of the state hidden inside your objects'
member variables. Sometimes, that lack of access is
entirely by design. By making your test function or class a
friend of the class under test, you can provide
access to those member variables without opening them up to
the world, nor needing to provide safe accessors and mutators.
Sometimes, though, if you only need read-only access to the
members of a class, it can be better to simply provide accessors
that are marked const and use those instead of adding
a friend.
STL Stream Support
C++'s Standard Template Library (STL) has I/O stream objects which
are preferred over conventional fread() and
fwrite()-style calls. std::istream and
std::ostream have a common interface that uses
operator functions to allow for I/O
with any sort of object. For example, the following is canonical
C++ style to implement a stream output operator:
class DataType { ... };
std::ostream &operator<<(std::ostream &os, const DataType &dt) {
return os << dt.value;
}
Often, the members you need to access from your operator<<
function will be private or protected. Because the operator functions are in
the top-level namespace, and are not a member of your class, you will need
to declare the operator functions as a friend:
class DataType {
public:
friend std::ostream &operator<<(std::ostream &os, const DataType &dt);
...
Splitting storage and algorithms
Some programmers are a fan of splitting storage off from higher-level algorithms,
but still want to provide a safe interface to the wider world despite the need
for the algorithms to dig deeper into storage. For example, our earlier example
of BankAccount may have a corresponding AuditProcess
class that needs to examine the details of BankAccount beyond the
published interface. By adding friend class AuditProcess; to
BankAccount, you can expose the details between friends, and still
avoid exposing those details to the world.
Like many of C++'s other features, the use of the friend
keyword is somewhat contentious across the developer community. Some
engineers would instead prefer to provide explicit accessors and
mutators, or even using a derived class instead. Consider the style
of the existing codebase that you are working with before widely
embarking on the use of friend; while there are situations
where you may need to use friend, you should strive to
fit in with existing style.
Another thing that may not be immediately obvious is that being a friend is not an attribute that derived classes inherit. Consider, for example, the following code hierarchy:
class DataType {
private:
int value;
public:
friend class MultiplyByTwo;
};
class MultiplyByTwo {
public:
virtual void multiply(DataType &dt) {
dt.value *= 2;
}
};
class MultiplyByThree : public MultiplyByTwo {
public:
virtual void multiply(DataType &dt) {
dt.value *= 3;
}
};
Note that MultiplyByThree will not successfully compile; the friendship granted to MultiplyByTwo does not apply to all child classes of MultiplyByTwo;
Just because I consider you a friend, it doesn't mean that you'll do the same
in return. Likewise, C++'s friend keyword is uni-directional; if
class A declares friend class B, that doesn't mean
class A will have access to class B's private members.