More C++ Idioms/Friendship and the Attorney-Client
< More C++ IdiomsAttorney-Client
Intent
Control the granularity of access to the implementation details of a class
Motivation
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 encapsulations. 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. It helps reduce coupling between classes. Attorney-Client idiom allows a class to precisely control the amount of access they give to their friends.
Solution and Sample Code
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 --- a C++ friend! The Attorney
class is crafted carefully to serve as a proxy to the Client
. Unlike a typical proxy class, Attorney
class replicates only a subset of Client
’s private interface. For instance, consider class Foo
wants to control access to its implementation details. For better clarity we rename it as Client
. 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. The Attorney
class has all inline static member functions, each taking a reference to an instance of the Client
and forwarding the function calls to it. Some things are idiomatic about the Attorney
class. Its implementation is entirely private, which prevents other unexpected classes gaining access to the internal of Client
. The Attorney
class 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 eventually the
Client
. Without the Attorney
class, Client
class 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
member function 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. The Derived::Func
function gets called via polymorphism. To access the implementation details of Derived
class, however, the same idiom may be applied.
#include <cstdio>
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) {
printf("Derived::Func\n"); // This is called even though main is not a friend of Derived.
}
public:
~Derived() {}
};
class Attorney {
private:
static void callFunc(Base & b, int x) {
return b.Func(x);
}
friend int main (void);
};
int main(void) {
Derived d;
Attorney::callFunc(d, 10);
}
Known Uses
References
Friendship and the Attorney-Client Idiom (Dr. Dobb's Journal)