The
friend
keyword in C++ drops the barriers of access control between a class and functions and/or other classes which are named in the
friend
declaration. It is a language feature that introductory tutorial text books seem to have a lot of trouble with. In searching for an example of its use, they often reach for that of declaring freestanding operator functions as
friend
s. In this article, I want to argue the case that using
friend
s in this way is a bad design decision – albeit for a more subtle reason than any that might immediately spring to mind – and also, that
friend
is not inherently evil. I will illustrate the latter with an example of how its use does genuinely make a design more robust.
A Bad Example
First, let’s dissect a simple example of using
friend
s to implement
operator<<
(note that the same arguments can be applied to many similar examples). Consider a simple (and self-explanatory) value based class:
class seconds
{
public:
explicit seconds(int initialiser);
//...
friend std::ostream&
operator<<(std::ostream& os, const seconds& s);
private:
int val;
};
std::ostream& operator<<(std::ostream& os, const seconds& s)
{
os << s.val;
return os;
}
The use of
friend
in this way is, in my experience, fairly common in C++ production code, probably because it is a traditional example used in C++ text books (indeed, no lesser book than
C++ Programming Language
[1] contains such an example). The immediately obvious way to give
operator<<
access to the implementation of seconds is to make it a member. However, the
operator<<
is something of a language design paradox, because there is no way to define it as a class member, while at the same time allowing its idiomatic use in client code. If
operator<<
were to be defined as a member of the class seconds, then it would not possible to write the simple expression:
std::cout << seconds(5);
This is because in such expressions it is idiomatic for the instance of the class of which
operator<<
is a member to appear on the left hand side of the << operator. If
operator<<
were made into a member of seconds, the expression would have to be written the other way around. Therefore, a non-member function must be used to implement this operator.
So, what’s wrong with this approach? Well, one concern is that encapsulation has been breached vis-à-vis the use of
friend
, to allow a non-member function to gain access to the implementation of
seconds
. However, that is clearly not really a concern (although in my experience many seem to think it is). After all, what really is the difference between a function having (private) implementation access because it’s a member or because it’s a
friend
? Another way of looking at it is this: a
friend
function is a member function via a different syntax.
In the above example, the real problem is this: the use of
friend
to implement a non-member operator is only necessary because a natural (and necessary) conversion is absent from
seconds
’ interface. In such class designs it makes perfect sense for instances to be convertible to a built-in type representation of their value. The addition of the
value()
member function underpins this, as follows:
class seconds
{
public:
explicit seconds(int initialiser);
//...
int value() const;
private:
int val;
};
std::ostream& operator<<(std::ostream& os, const seconds& s)
{
os << s.value();
return os;
}
Allowing the value to be converted to a built in type representation via a member function is not only a good thing, it is also necessary in order to make the class usable. For example, a class designer can not know in advance of every conversion client code will need. The provision of the
value(
) member function allows any required conversion to be added without modifying the definition of
seconds
. Note the analogy with
std::string
which permits conversion to
const char*
via the
c_str()
member function. Note further, the use of a member function rather than a conversion operator, thus requiring the conversion to be a deliberate decision on the part of
seconds
’ user.
Now
operator<<
can be implemented as a non-member, and there is no need to use
friend
. However, I now want to describe a short piece of design work, in which
friend
is used for the right reasons…
A Persistent Object Framework
Consider the design of a persistence framework that has the following requirements:
- The state of certain objects must transcend their existence in a C++ program. Such objects are typically those from the problem (real world) domain. When such objects are not in use in the C++ program, their state is stored in some kind of repository, let’s say, a relational database.
- Such objects must be loaded into memory when they are needed, and consigned to the database when they are finished with. For the sake of this example, the loading of objects and their consignment to the database should be transparent to the applications programmer.
The housekeeping of whether the object has been retrieved or not is delegated to a (class template) smart pointer called
persistent_ptr
.
persistent_ptr
delegates the mechanism used to retrieve objects from the database to the implementations of an interface class called
database_query
. The definitions look like this:
template <class persistent>
class database_query
{
public:
typedef persistent persistent_type;
virtual persistent_type* execute() const = 0;
};
template <typename persistent>
class persistent_ptr
{
public:
~persistent_ptr()
{
...
}
// ...
persistent* operator->()
{
return get();
}
persistent const* operator->() const
{
return get();
}
private:
persistent* get() const
{
if (!loaded(object)) object = query->execute();
return object;
}
boost::scoped_ptr< database_query<persistent> > const query;
persistent* object;
};
An illustration of persistant_ptr’s use looks like this:
void f()
{
persistent_ptr object(…);
:
:
}
The
object
is instantiated, its state loaded when/if a call to it is made, and when object goes out of scope its state (if loaded) goes back into the database. The interface class
database_query
defines the protocol for loading objects from the database into memory. It has just one member function: the
execute()
function.
persistant_ptr
’s member access operator checks if the object (of type
persistent
) is loaded and if not, calls the
database_query
’s
execute()
function, i.e.
lazy loading
is used and the object is not loaded until it is actually used. Also, the invocation of
persistant_ptr
’s destructor triggers the consigning of
persistent
to the database. So far so good. However, we are now presented with a problem: what if the
database_query
’s
execute()
function was to be called (contrary to the idea of this design) by something other than
persistent_ptr
? One option is just to document that this is not the way this framework is intended to be used. However, it would be much better if this constraint could be stated explicitly by the code itself. There is a way to do this…
A Simple Solution Using friend
Like many (most?) of the textbook examples of the use of
friend
, the above example featured its use in accessing private member data. However, member functions too can be made private, and one way to prevent unauthorised parties calling
database_query
’s
execute()
function is to make it private. This leaves us with a problem:
persistent_ptr
can’t call it either. One simple solution is to declare
persistent_ptr
a
friend
. Given that
database_query
is an interface class and the execute() function is the only member function, this does not cause any problems with exposure of
all
private members – a problem normally associated with
friend
. The interface class
database_query
now looks like this:
template <class persistent>
class database_query
{
public:
typedef persistent persistent_type;
private:
friend class persistent_ptr<persistent>;
virtual persistent_type* execute() const = 0;
};
Note that there is no impact on derived classes because friendship is not inherited. The use of
friend
in this way has provided one simple way to bring some extra robustness to the design of this framework.
Finally
When designing for C++, occasionally there is a need for two classes to work together closely. In such cases the permitted interactions must often be defined quite specifically, to an extent not covered by the access specifiers public, protected and private. Here, the
friend
keyword can be an asset. I believe many (most?) of its traditional uses – both in textbook examples and production code – are bad, as illustrated by the
seconds
example. However, it should not be rejected as bad in every case, as I believe the example of
persistent_ptr
and its colleague
database_query
shows.
Mark Radford
mark@twonine.co.uk
References
1 Stroustrup,Bjarne (1997) C++ Programming Language, 3rd edition, Addison-Wesley.