Friends
What is a friend
?
Something to allow your class to grant access to another class or function.
Friends can be either functions or other classes. A class grants access privileges to its friends. Normally a developer
has political and technical control over both the friend
and member functions of a class (else you may need to get
permission from the owner of the other pieces when you want to update your own class).
Do friends violate encapsulation?
No! If they’re used properly, they enhance encapsulation.
“Friend” is an explicit mechanism for granting access, just like membership. You cannot (in a standard conforming program) grant yourself access to a class without modifying its source. For example:
class X {
int i;
public:
void m(); // grant X::m() access
friend void f(X&); // grant f(X&) access
// ...
};
void X::m() { i++; /* X::m() can access X::i */ }
void f(X& x) { x.i++; /* f(X&) can access X::i */ }
For a description on the C艹 protection model, see D&E sec 2.10 and TC艹PL sec 11.5, 15.3, and C.11.
You often need to split a class in half when the two halves will have different numbers of instances or different lifetimes. In these cases, the two halves usually need direct access to each other (the two halves used to be in the same class, so you haven’t increased the amount of code that needs direct access to a data structure; you’ve simply reshuffled the code into two classes instead of one). The safest way to implement this is to make the two halves friends of each other.
If you use friends like just described, you’ll keep private
things private
. People who don’t understand this often
make naive efforts to avoid using friendship in situations like the above, and often they actually destroy
encapsulation. They either use public
data (grotesque!), or they make the data accessible between the halves via
public
get()
and set()
member functions. Having a public
get()
and set()
member function for a private
datum is okay only when the private
datum “makes sense” from outside the class (from a user’s perspective). In many
cases, these get()
/set()
member functions are almost as bad as public
data: they hide (only) the name of the
private
datum, but they don’t hide the existence of the private
datum.
Similarly, if you use friend functions as a syntactic variant of a class’s public
access functions, they don’t violate
encapsulation any more than a member function violates encapsulation. In other words, a class’s friends don’t violate
the encapsulation barrier: along with the class’s member functions, they are the encapsulation barrier.
(Many people think of a friend function as something outside the class. Instead, try thinking of a friend function as part of the class’s public interface. A friend function in the class declaration doesn’t violate encapsulation any more than a public member function violates encapsulation: both have exactly the same authority with respect to accessing the class’s non-public parts.)
What are some advantages/disadvantages of using friend
functions?
They provide a degree of freedom in the interface design options.
Member functions and friend
functions are equally privileged (100% vested). The major difference is that a friend
function is called like f(x)
, while a member function is called like x.f()
. Thus the ability to choose between
member functions (x.f()
) and friend
functions (f(x)
) allows a designer to select the syntax that is deemed most
readable, which lowers maintenance costs.
The major disadvantage of friend
functions is that they require an extra line of code when you want dynamic binding.
To get the effect of a virtual
friend
, the friend
function should call a hidden (usually protected
)
virtual
member function. This is called the Virtual Friend Function Idiom.
For example:
class Base {
public:
friend void f(Base& b);
// ...
protected:
virtual void do_f();
// ...
};
inline void f(Base& b)
{
b.do_f();
}
class Derived : public Base {
public:
// ...
protected:
virtual void do_f(); // "Override" the behavior of f(Base& b)
// ...
};
void userCode(Base& b)
{
f(b);
}
The statement f(b)
in userCode(Base&)
will invoke b.do_f()
, which is virtual
. This means
that Derived::do_f()
will get control if b
is actually a object of class
Derived
. Note that Derived
overrides
the behavior of the protected
virtual
member function do_f()
; it does not have its own
variation of the friend
function, f(Base&)
.
What does it mean that “friendship isn’t inherited, transitive, or reciprocal”?
Just because I grant you friendship access to me doesn’t automatically grant your kids access to me, doesn’t automatically grant your friends access to me, and doesn’t automatically grant me access to you.
- I don’t necessarily trust the kids of my friends. The privileges of friendship aren’t inherited. Derived classes of a
friend aren’t necessarily friends. If
class
Fred
declares thatclass
Base
is afriend
, classes derived fromBase
don’t have any automatic special access rights toFred
objects. - I don’t necessarily trust the friends of my friends. The privileges of friendship aren’t transitive. A friend of a
friend isn’t necessarily a friend. If
class
Fred
declaresclass
Wilma
as a friend, andclass
Wilma
declaresclass
Betty
as a friend,class
Betty
doesn’t necessarily have any special access rights toFred
objects. - You don’t necessarily trust me simply because I declare you my friend. The privileges of friendship aren’t
reciprocal. If class
Fred
declares that classWilma
is a friend,Wilma
objects have special access toFred
objects butFred
objects do not automatically have special access toWilma
objects.
Should my class declare a member function or a friend
function?
Use a member when you can, and a friend
when you have to.
Sometimes friends are syntactically better (e.g., in class
Fred
, friend functions allow the Fred
parameter to be
second, while members require it to be first). Another good use of friend
functions are the binary infix arithmetic
operators. E.g., aComplex + aComplex
should be defined as a friend rather than a member if you want to allow
aFloat + aComplex
as well (member functions don’t allow promotion of the left hand argument, since that would change
the class of the object that is the recipient of the member function invocation).
In other cases, choose a member function over a friend
function.