Friendship and the Attorney-Client Idiom

The much-maligned "friend" declaration comes with its share of pitfalls. Alan shows how to use friendship to enhance encapsulation while minimizing the risks.


January 01, 2006
URL:http://www.drdobbs.com/friendship-and-the-attorney-client-idiom/184402053

January, 2006: Friendship and the Attorney-Client Idiom

Alan R. Bolton is a Senior Development Engineer at Extended Systems in Boise, Idaho, specializing in data-synchronization software. He can be reached at [email protected].


The Three Ps


There is a long-standing prejudice in the C++ community against declaring friend functions and classes. Since the earliest days of C++, people have claimed that friendship breaks encapsulation [1]; and although the argument is by now largely settled in favor of the judicious use of friendship, the charge continues to be made. Programming books still appear on the market describing friend declarations as indicators of poor design [2], and respected C++ experts say things like "[I avoid] friendship like the plague," [3] and "It is always best to use a friend declaration with a guilty conscience" [4].

One factor in this distrust is friendship's lack of granularity: A class grants its friends access to every detail of its implementation, which seems to violate the spirit, if not the letter, of encapsulation. Of course, the whole point of friendship is to allow coupling when appropriate; but the access control tools provided by C++ are conspicuously absent from the class-friend relationship, and without them the coupling can easily become too tight. This has long been seen as a weakness of the language, and proposals to address it have been considered and abandoned in the past [5]. Because of this weakness, it is essential to understand how to use friendship in a manner consistent with good design.

This article summarizes the generally accepted rules for the use of free friend functions, and suggests some guidelines for the use of friend classes and class members. It also addresses the question of which class members a friend should be allowed to use (in other words, the characteristics of the "private interface" that should be offered to friends). Finally, it describes the "Attorney-Client" idiom, a lightweight mechanism that helps a class specify this private interface, thereby enabling it to create contracts with its friends.

Guidelines for Friend Functions

Scott Meyers has developed a straightforward algorithm [6, 7] for deciding, given a class and a function, whether the function should be implemented as a class member, a free friend function, or a free nonfriend function. It is reproduced here in a slightly modified form (see Figure 1).

Examining the single case where the function should be implemented as a free friend, it's clear that friendship is appropriate only in the case where (a) the expected usage of the function dictates that it be a free function; and (b) the function cannot be implemented using just the public interface of the class. (For a similar but less formal treatment of this topic, see the accompanying sidebar entitled "The Three Ps.")

Guidelines for Friend Classes and Class Members

In the interest of brevity, I will begin by defining some terms:

Now, observe that "friend class" is simply shorthand notation for a class whose members are all declared friends [10]. This reduces the problem to determining whether an individual class member should be declared a friend method. There are three general cases where this question might arise:

  1. You are designing two or more classes to work together collaboratively.
  2. You discover (during maintenance or feature enhancement) that one class "needs" access to some private member data of another.
  3. You discover that one class "needs" access to some private member function(s) of another.

Note the quotes around the word "needs." Most of the decision about using friendship hinges on just how real that need turns out to be.

Upon reflection, it should be evident that the first case is really a special case of the second and third. The only difference is that if you begin with the assumption that your classes will be more closely coupled, you will probably be more predisposed to assume that the need is real. Other than that, the exact same principles apply.

Similarly, the second case is identical to the third. Conceptually, any access to a data member of a class is equivalent to using an inline accessor (set or get) function. In fact, in the case of (potential) friendship, an accessor function is the preferred mechanism (for reasons that will become clear below). So, when you consider how your potential friend methods might access the private data of the grantor, imagine them using inline accessor functions.

The problem can now be restated as: Given classes F and G, where F has some member function foo() that needs to call a private member p() of G, what should you do? Your choices are:

  1. Make G::p() public;
  2. Refactor your design;
  3. Make F:foo() a friend of G.

Due to the combination of bias against friendship and reluctance to refactor code, it's a safe bet that number 1 is the most frequently chosen option. We've all done it—even you. It's also usually the worst option of the three. Rather than giving your classes the capabilities that they need, or weakening encapsulation by coupling two unrelated classes, making G::p() public compromises the entire public interface of G. This has negative consequences for documentation and testing, just for starters, and is an invitation to all sorts of maintenance problems down the road. Arguably, encapsulation is more broken in this case than it would be by declaring F::foo() a friend.

Of course, sometimes making G::p() public is the right answer. Ask the same questions you would anytime you consider adding a public function to a class: Does it provide an essential service to clients (other than F::foo())? Is its usage consistent with that of the other public members? Does it sufficiently hide G's implementation details? If you can honestly say that p() belongs in G's public interface, then congratulations! You are done.

In most cases, though, you must at least consider refactoring. A full treatment of the topic is beyond the scope of this article, but your choices are likely to involve either moving foo() into G, moving p() into F, or moving both foo() and p() into some other class. There are several well-known refactorings that may prove useful, including Move Method, Move Field, Extract Class, and Remove Middle Man [11]. However, it's important to realize that in many cases, refactoring, though desirable, may not be feasible. Particularly with legacy code, factors such as time constraints, code complexity, and lack of unit tests may all be compelling arguments against attempting to refactor (at least in the near term).

So if you don't want to add a public member function, and refactoring is not an option, friendship is the only alternative left. The question, then, is how to minimize the coupling between the grantor and its friend(s). Here are a few guidelines:

Enforcing the Contract

I referred above to providing friends with access only to the private, nonvirtual member functions of the grantor, and of establishing a contract between the grantor and its friends. How is this possible, given that friendship in C++ is an all-or-nothing proposition? One way is to use the Attorney-Client idiom, which allows grantors to precisely control the amount of access they give their friends.

The implementation is quite simple (see Listing 1). In the grantor class, declare a friend class to serve as the grantor's "Attorney." The Attorney class implementation is entirely private and consists only of inline static member functions, each taking a reference to an instance of the grantor class as its first argument and calling a corresponding member function on the grantor instance with the remaining arguments. It also declares a set of friends (free functions, classes, and class member functions) that will be allowed access to its implementation.

In effect, the Attorney acts as a middleman, defining an interface that serves as a proxy for the grantor, and declaring (as its own friends) who will be allowed to use the interface. Only those functions can use the Attorney's private implementation to access the private members of the grantor. Like a real-life attorney, it knows all of its client's secrets, but it only shares some of them with a small number of outsiders.

Variations

Some variations on this idiom can be useful for specific cases. For instance, a "Corporate Counsel" class may function as the Attorney for several related classes. Its interface resembles the union of two or more individual Attorney classes. This is a natural variation to use when two or more classes need to declare an identical set of friends. Another variation is the "Consigliere," where the Attorney is a nested class of the grantor. This keeps the definition of the interface, as well as the set of friends, entirely under the control of the grantor class. Finally, just as in real life, you can use different Attorneys to represent you in different situations. If different friends need access to different sets of your private members, you can easily define a separate Attorney class for each one. Again, you will benefit from looser coupling and better encapsulation.

Performance Issues

You can have as many Attorneys as you can afford. But how many is that? What are the costs of using this idiom? Are Attorneys as expensive in this context as they are in real life?

Fortunately, there is a minimal runtime cost to using Attorneys. Because they are basically a collection of inline functions that pass through to their client objects, they are very efficient—in most cases, as efficient as directly accessing data members or calling member functions in the grantor class. Even more complex functions that take or return nontrivial objects generally give comparable results, thanks to the Return Value Optimization [15]. For a Visual C++ 6.0 project that demonstrates the overhead of using an Attorney class in various situations, see ACDemo.zip (available at http://www.cuj.com/code/).

The main cost of this idiom is increased complexity in your code. A proliferation of Attorney classes might obscure the meaning of the relationships between your grantor classes and their friends, and confuse maintainers who are unfamiliar with the idiom. However, this is a danger with any idiom, and the key to preventing it is by providing thorough documentation. And, given that friendship is an infrequently used language feature, I believe that the benefits of this technique far outweigh the costs.

Conclusion

Historically, many C++ developers have seen friendship as incompatible with strong encapsulation, and a symptom of poor design. However, when used responsibly, friendship can actually enhance encapsulation. Using friendship properly requires not only understanding when it is the appropriate solution, but also how to structure the interface between a class and its friends.

Once this interface is established, it must be enforced. Although C++ does not provide direct support for a class-friend interface, the Attorney-Client idiom can be used to create one. This idiom, which is simple to implement and efficient to use, allows a class to go beyond simply declaring a set of friends; it enables the creation of contracts with those friends, and allows classes to grant friendship without compromising their privacy.

Acknowledgments

Thanks to Darren Blaser, Patrick Harper, Vanessa Hutchison, Janice Kaltenecker, Gary Keskela, Jim Krahn, and Bill Lish for their insightful comments.

References

  1. Stroustrup, Bjarne. The Design and Evolution of C++, Addison-Wesley, 1994, p. 53.
  2. Misfeldt, Trevor, et al. The Elements of C++ Style, Cambridge University Press, 2004, p. 77.
  3. Wilson, Matthew. "Friendly Templates," C/C++ Users Journal Experts Forum, December 2003; http://www.cuj.com/ documents/s=8943/cujexp0312wilson2/.
  4. Josuttis, Nicolai M. Object-Oriented Programming in C++, John Wiley and Sons, 2001.
  5. Stroustrup, Bjarne. The Design and Evolution of C++, Addison-Wesley, 1994, pp. 55-56.
  6. Meyers, Scott. Effective C++, Second Edition, Addison-Wesley, 1998, Item 19.
  7. Meyers, Scott. "How Non-Member Functions Improve Encapsulation," C/C++ Users Journal, February 2000.
  8. Cline, Marshall, et al. C++ FAQs, Second Edition, Addison-Wesley, 1999, Item 19.11. Note that although in this item the criteria are used for determining friendship, they are really criteria for when a free function should be used. This fact is glossed over in their discussion, which centers around the notion of transforming a member function into a friend; thus they don't mention Meyers' key insight, which is that a friend is simply a "3 P" function that happens to need access to T's innards.
  9. ibid., Item 19.06.
  10. Stroustrup, Bjarne. The C++ Programming Language, Third Edition, Addison-Wesley, 1997, Section 11.5.
  11. Fowler, Martin. Refactoring: Improving the Design of Existing Code, Addison-Wesley, 2000.
  12. Lakos, John. Large Scale C++ Software Design, Addison-Wesley, 1996, p. 137.
  13. Gamma, Erich, et al. Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1995, p. 325.
  14. Cline, Marshall, et al. C++ FAQs, Second Edition, Addison-Wesley, 1999, Item 19.10.
  15. Bulka, Dov and David Mayhew. Efficient C++: Performance Programming Techniques, Addison-Wesley, 2000.

CUJ

January, 2006: Friendship and the Attorney-Client Idiom

Figure 1: The Meyers algorithm.

Given a class T and a function f:

if (f needs to be virtual)
{
    f should be a member function of T;
}
else if ( (f is operator>>) or (f is operator<<) or 
	            (f needs type conversions on its left-most argument) )
{
    f should be a non-member function;
    if (f needs access to non-public members of T)
    {
        f should be a friend of T;
    }
}
else if (f can be implemented via T's public interface)
{
    f should be a non-member function;
}
else
{
    f should be a member function of T;
}

January, 2006: Friendship and the Attorney-Client Idiom

Listing 1

// The Grantor class, its attorney GrantorAttorney,
// and the free functions that are given controlled access to
// Grantor by way of GrantorAttorney

#ifndef __GRANTOR_H__
#define __GRANTOR_H__

class Grantor
{
public:
   Grantor();
   ~Grantor();

   /*

    ...

    */

private:
   // There's a lot in this class that we don't want anyone
   // else to see...
   /*

    ...

    */

   // But there are a couple of member functions we want
   // to expose to our friends
   void foo(int x);
   int bar() const;

   // Declare our "attorney" as a friend
   friend class GrantorAttorney;
};

// Definition of the GrantorAttorney class should be in the same
// header as Grantor's definition
class GrantorAttorney
{
private: // private is not actually necessary here, since there are   
        // no public members of this class
   static void foo(Grantor& g, int x)
   { g.foo(x); }

   static int bar(const Grantor& g)
   { return g.bar(); }

   // These are the only functions that will be able to access the
   // attorney's private members, and through them the private
   // members of the grantor. Class members or whole classes could
   // be declared friends as well:

   friend void do_foo(int x);
   friend int do_bar(const Grantor&);
};

// do_foo and do_bar would normally be defined somewhere else.
// They're just here for illustrative purposes.

inline void do_foo(int x)
{
   Grantor g;
   GrantorAttorney::foo(g, x);
}

inline int do_bar(const Grantor& g)
{
   return GrantorAttorney::bar(g);
}


#endif // __GRANTOR_H__

January, 2006: Friendship and the Attorney-Client Idiom

The Three Ps

Another way to identify candidates for friendship is by using the "Three Ps" (Position, Promotion, and Perception), as suggested by Cline et al. [8]. "Position" refers to the case where the object being operated on cannot be the left-most argument. The stream-insertion operators are the most common example of this case, so Position is really a generalization of the "f is operator<<" and "f is operator>>" cases in the Meyers algorithm. Similarly, "Promotion" is simply another way of saying that type conversions are needed on the left-most argument. Finally, "Perception" is an esthetic judgment as to whether or not the calling syntax is improved by using a free function. Cline and his coauthors appear to be among that rare breed who actually like friend functions, going so far as to state that a friend should be preferred over a member function "whenever it improves the readability of the code" [9]. Although Meyers takes a similar view with respect to free functions in general, he is more conservative in promoting the use of friends; in his algorithm, the use of a friend function is dictated solely by need, not esthetics. And, as if to drive home the point, he states, "Whenever you can avoid friend functions, you should, because, much as in real life, friends are often more trouble than they're worth" [6].

CUJ

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.