Picky Friendship: Access Control in C++

Sander Stoks

When your friend wants to borrow one of your books, you'd probably give it without second thoughts – that's what friends are for. When it comes to your car, you'd probably be a little more picky. And you may want to keep your toothbrush strictly for yourself.

In C++, there's no such distinction. A friend is a friend, and everything you consider private is shared equally among them. "Mi clasa es tu clasa".

In this article, I present a way to implement selective access to the interfaces a class implements. Sometimes you want to allow certain classes access to specific interfaces, but declaring those classes friend means they get access to all your member functions - and even to your private member data. You could declare those specific member functions protected, limiting access to classes inheriting from yours, but there is no way to limit this inheritance to only certain "heirs".

The limitation of the current technique is that the granularity is on the interface level, so you would have to put the "precious" member functions in their own interfaces. Although a little far-fetched, let's stick with the example of the first paragraph, and define the interfaces

struct BookLender
{
    virtual void BorrowBook() = 0;
};

struct CarLender
{
    virtual void BorrowCar() = 0;
};

And further suppose you have a class representing a person, implementing these interfaces:

class Person : public BookLender, public CarLender
{
public:
    // anyone can borrow some sugar
    void BorrowSugar() { cout << "Here's some sugar" << endl; } 

private:
    void BorrowBook()  { cout << "Here's a book" << endl; }
    void BorrowCar()   { cout << Here's my car" << endl; }

    Toothbrush m_Toothbrush;    // nobody touches my toothbrush!
};

First, let's make sure we don't allow access to the BookLender and CarLender interfaces by accident. Although they are private member functions, anyone could simply do,

Person p;
CarLender* c = &p;
c->BorrowCar();

You could make it derive protected instead, and also do the "sealed" trick:

class Sealed
{
    Sealed();
    friend class Person;
};

class Person : virtual Sealed, protected BookLender, protected CarLender
{
public:
    void BorrowSugar() { } 

protected:
    void BorrowBook() { }
    void BorrowCar() { }

private:
    Toothbrush m_Toothbrush;
};

This way, nobody can inherit from Person (and consequently, access the protected member functions) unless they are listed as friends in the Sealed class. This is better than making these "trusted" classes outright friends of the Person class since those "selected heirs" can not access your private toothbrush, but there is still no discrimination between friends who can borrow a book and friends who can borrow your car. Besides, we should forget about this whole inheritance thing as a mechanism to control your client code, as it is not the correct paradigm for this.

Let's derive from the interfaces privately instead:

class Person : private BookLender, private CarLender
{
public:
    void BorrowSugar() { } 

private:
    void BorrowBook() { }
    void BorrowCar() { }

    Toothbrush m_Toothbrush;
};

This cuts short any attempts to access the private interfaces directly, since

Person p;
CarLender* c = &p;
c->BorrowCar();

will give a compiler error like "conversion from Person* to CarLender* exists, but is inaccessible". So far, so good.

Now suppose there's a class Colleague, which you'd like to allow borrowing your books, but not your car. Declaring Colleague a friend class is not an option, since then any Colleague not only can borrow your car, but has full access to your toothbrush – yuck. Even a class GoodFriend is not allowed access to your toothbrush, but borrowing the car is fine. In code, we want something like this:

class Colleague
{
public:
    Colleague(Person& p) : m_p(p) {}
    void CallPrivateFunctions()
    {
        m_p.BorrowBook();        // allowed
        m_p.BorrowCar();         // not allowed
        use(m_p.m_Toothbrush);   // not allowed
    }
    
private:
    Person& m_p;
};

class GoodFriend
{
public:
    GoodFriend(Person& p) : m_p(p) {}
    void CallPrivateFunctions()
    {
        m_p.BorrowBook();        // allowed
        m_p.BorrowCar();         // allowed
        use(m_p.m_Toothbrush);   // not allowed
    }
    
private:
    Person& m_p;
};

But how can we get this to work, with as little extra code as possible, and preferably with an intuitive syntax? The trick lies in a little helper template class:

template <class C, class Itf> 
class Access 
{ 
protected: 
    Access(C& c) : m_class(c) {} 
    operator Itf*() { return &m_class; } 
    C& m_class; 
};

If you now extend your Person class a little bit:

class Person : private BookLender, private CarLender
{
public:
    void BorrowSugar() { } 

    template <class C, class Itf> friend class Access;
    
    class BookAccess : public Access
    {
        BookAccess(Person& p) : Access(p) {}
        friend class Colleague;
        friend class GoodFriend;
    };

    class CarAccess : public Access
    {
        CarAccess(Person& p) : Access(p) {}
        friend class GoodFriend;
    };

private:
    void BorrowBook() { }
    void BorrowCar() { }

    Toothbrush m_Toothbrush;
};

the only thing you need to add to the friend classes is the following:

class Colleague
{
public:
    Colleague(Person& p) : m_p(p) {}
    void CallPrivateFunctions()
    {
        BookLender* b = Person::BookAccess(m_p);  // allowed
        b->BorrowBook();                          // allowed
        
        CarLender* c = Person::CarAccess(m_p);    // not allowed
        c->BorrowCar();                           // not allowed
        
        use(m_p.m_Toothbrush);                    // not allowed
    }
    
private:
    Person& m_p;
};

class GoodFriend
{
public:
    GoodFriend(Person& p) : m_p(p) {}
    void CallPrivateFunctions()
    {
        BookLender* b = Person::BookAccess(m_p);  // allowed
        b->BorrowBook();                          // allowed
        
        CarLender* c = Person::CarAccess(m_p);    // allowed
        c->BorrowCar();                           // allowed
        
        use(m_p.m_Toothbrush);                    // not allowed
    }
    
private:
    Person& m_p;
};

The idea is that via the Access class, you allow others (but only friends!) to use a conversion operator which yields access to the "raw" interfaces. You define such a class once per interface you want to share, and list anyone as friend in that class definition whom you like to grant access to this particular interface.

Finally, if you grew up in the "MFC/COM/ATL camp", you will perhaps appreciate some preprocessor help to make things more readable:

#define ENABLE_ACCESS template  friend class Access 

#define GRANT_ACCESS(C, itf)                \
class itf##Access : public Access<C, itf>   \
{                                           \
    itf##Access(C& c) : Access<C, itf>(c) {}

#define END_GRANT_ACCESS }
#define BY friend class

using these macros, the code for Person becomes:

class Person : private BookLender, private CarLender
{
public:
    void BorrowSugar() { } 

    ENABLE_ACCESS;
    
    GRANT_ACCESS(Person, BookLender);
        BY Colleague;
        BY GoodFriend;
    END_GRANT_ACCESS;
    
    GRANT_ACCESS(Person, CarLender);
        BY GoodFriend;
    END_GRANT_ACCESS;

private:
    void BorrowBook() { }
    void BorrowCar() { }

    Toothbrush m_Toothbrush;
};

By the way: my introductionary computer programming book is available here. It uses C and stays away from the type of exotic Spielerei described in this article.