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
friend
ship 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.