Friend or Foe!

Friend or Foe!

By Mark Radford

Overload, 14(71):18-19, February 2006


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.






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.