More C++ Idioms/Friendship and the Attorney-Client

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Attorney-Client
[edit]

Intent[edit]

Control the granularity of access to the implementation details of a class

Motivation[edit]

A friend declaration in C++ gives complete access to the internals of a class. Friend declarations are, therefore, frowned upon because they break carefully crafted encapsulation. The friendship feature of C++ does not provide any way to selectively grant access to a subset of private members of a class. Friendship in C++ is an all-or-nothing proposition. For instance, the following class Foo declares class Bar its friend. Class Bar therefore has access to all the private members of class Foo. This may not be desirable because it increases coupling. Class Bar cannot be distributed without class Foo.

class Foo 
{
private:
  void A(int a);
  void B(float b);
  void C(double c);
  friend class Bar;
};

class Bar {
// This class needs access to Foo::A and Foo::B only.
// C++ friendship rules, however, give access to all the private members of Foo.
};

Providing selective access to a subset of members is desirable because the remaining private members can change interface if needed without breaking client code. It helps reduce coupling between classes. The Attorney-Client idiom allows a class to precisely control the amount of access they give to their friends.

Solution and Sample Code[edit]

The Attorney-client idiom works by adding a level of indirection. A client class that wants to control access to its internal details, appoints an attorney and makes it a friend. The Attorney class is carefully crafted to serve as a proxy to the Client. Unlike a typical proxy class, Attorney replicates only a subset of Client’s private interface. For instance, consider class Client wants to control access to its implementation details. Client wants its Attorney to provide access to Client::A and Client::B only.

class Client 
{
private:
  void A(int a);
  void B(float b);
  void C(double c);
  friend class Attorney;
};

class Attorney {
private:
  static void callA(Client & c, int a) {
    c.A(a);
  } 
  static void callB(Client & c, float b) {
    c.B(b);
  }
  friend class Bar;
};

class Bar {
// Bar now has access to only Client::A and Client::B through the Attorney.
};

The Attorney class restricts access to a cohesive set of functions. All methods of class Attorney are inline static, each taking a reference to an instance of the Client and forwarding the function calls to it. Some things about Attorney are idiomatic. Its implementation is entirely private, which prevents other unexpected classes gaining access to the internals of Client. Attorney determines which other classes, member functions, or free functions get access to it. It declares them as friend to allow access to its implementation and through it Client. Without Attorney, Client would have declared the same set of friends giving them unrestrained access to the internals of Client.

It is possible to have multiple attorney classes providing access to different sets of implementation details of the client. For instance, class AttorneyC may provide access to the Client::C method only. An interesting case emerges where an attorney class serves as a mediator for several different classes and provides cohesive access to their implementation details. Such a design is conceivable in case of inheritance hierarchies because friendship in C++ is not inheritable, but private virtual function overrides in derived classes can be called if base's private virtual functions are accessible. In the following example, the Attorney-Client idiom is applied to class Base and the main function. Derived::Func gets called via polymorphism. To access the implementation details of Derived, however, the same idiom may be applied.

#include <iostream>

class Base {
private:
  virtual void Func(int x) = 0;
  friend class Attorney;
public:
  virtual ~Base() {}
};

class Derived : public Base {
private:
  virtual void Func(int x)  {
    // This is called even though main is not a friend of Derived.
    cout << "Derived::Func" << endl;
  }

public:
  ~Derived() {}
};

class Attorney {
private:
  static void callFunc(Base & b, int x) {
    return b.Func(x);
  }
  friend int main();
};

int main() {
  Derived d;
  Attorney::callFunc(d, 10);
}

Known Uses[edit]

Related Idioms[edit]

References[edit]