Passkey Idiom and Better Friendship in C++

The friend keyword in C++ has always been clouded by controversy, often being accused of breaking encapsulation.

This article aims to present an existing but lesser-known idiom that allows you to limit the scope of the friend declaration. Let's take the example of a citizen class that has some public and private data.

class Citizen {
    public:
    string getName() const;
    
    private:
    string getFavouriteFood() const;
    string getSocialSecurityNumber() const;
    
    string _name;
    string _favouriteFood;
    string _socialSecurityNumber;
};

string Citizen::getName() const {
    return _name;
}

string Citizen::getFavouriteFood() const {
    return _favouriteFood;
}

string Citizen::getSocialSecurityNumber() const {
    return _socialSecurityNumber;
}

Let's now imagine a government class that needs to print a citizen's social security number.

class Government {
    private:
    void printCitizenInfo(const Citizen &citizen) const;
};

void Government::printCitizenInfo(const Citizen &citizen) const {
    cout << "Citizen Name: " << citizen.getName() << endl;
    cout << "Citizen SSN: " << citizen.getSocialSecurityNumber() << endl; // COMPILE ERROR
}

We can also imagine a spy class that is trying to subvert the citizen's private information.

class Spy {
    private:
    void printCitizenInfo(const Citizen &citizen) const;
};

void Spy::printCitizenInfo(const Citizen &citizen) const {
    cout << "Citizen Name: " << citizen.getName() << endl;
    cout << "Citizen SSN: " << citizen.getSocialSecurityNumber() << endl; // COMPILE ERROR
}

Both the government and the spy currently fail to access the citizen's private information.

What can we do to give the government access without making the citizen's information public? A successful but heavy-handed approach would be to make the government class a friend of the citizen class.

class Citizen {
    friend class Government;

    public:
    string getName() const;
    
    private:
    string getFavouriteFood() const;
    string getSocialSecurityNumber() const;
    
    string _name;
    string _favouriteFood;
    string _socialSecurityNumber;
};

Now, the government can access the social security number and the spy cannot. The side effect, however, is that the government now knows the citizen's favourite food. That's obviously an unacceptable privacy violation!

You're first reflex may be to think of using friend functions instead but, in fact, that doesn't solve the problem because a friend function will also have access to all of the citizen's information which is what we are trying to prevent.

One solution would be to redesign our classes like this:

class Citizen {
    public:
    string getName() const;
    void getSocialSecurityNumber(Government &government) const;
    
    private:
    string getFavouriteFood() const;
    string getSocialSecurityNumber() const;
    
    string _name;
    string _favouriteFood;
    string _socialSecurityNumber;
};

void Citizen::getSocialSecurityNumber(Government &government) {
    government.setSocialSecurityNumber(*this, _socialSecurityNumber);
}

class Government {
    public:
    void setSocialSecurityNumber(const Citizen &citizen, string socialSecurityNumber);
    
    private:
    void printCitizenInfo(const Citizen &citizen) const;
    string findSocialSecurityNumber(const Citizen &citizen) const;

    map<Citizen, string> _socialSecurityNumberMap;
};

string Government::printCitizenInfo(const Citizen &citizen) const {
    cout << "Citizen Name: " << citizen.getName() << endl;
    cout << "Citizen SSN: " << findSocialSecurityNumber(citizen) << endl;
}

Now, the citizen is explicitly giving it's social security number to the government class. The government class can now squirrel away the citizen's  social security number in a map, for instance, so it can later use it to print the citizen's information.

There's a more elegant solution that allows the citizen class to give the government class direct access to the information it wants. It's called the Passkey idiom. It helps us restrict the friend declaration's scope to one or more methods instead of the whole class.

template <typename T>
class Passkey {
    private:
    friend T;
    Passkey() {}
    Passkey(const Passkey&) {}
    Passkey& operator=(const Passkey&) = delete;
};

class Citizen {
    public:
    string getName() const;
    string getSocialSecurityNumber(Passkey<Government>) const;
    
    private:
    string getFavouriteFood() const;
    
    string _name;
    string _favouriteFood;
    string _socialSecurityNumber;
};

class Government {
    private:
    void printCitizenInfo(const Citizen &citizen) const;
};

void Government::printCitizenInfo(const Citizen &citizen) const {
    cout << "Citizen Name: " << citizen.getName() << endl;
    cout << "Citizen SSN: " << citizen.getSocialSecurityNumber(Passkey<Government>()) << endl; // THIS COMPILES
}

class Spy {
    private:
    void printCitizenInfo(const Citizen &citizen) const;
};

void Spy::printCitizenInfo(const Citizen &citizen) const {
    cout << "Citizen Name: " << citizen.getName() << endl;
    cout << "Citizen SSN: " << citizen.getSocialSecurityNumber(Passkey<Government>()) << endl; // COMPILE ERROR
}

Only the Government class can instantiate the correct passkey: the Spy implementation fails to compile because it can't instantiate the private Passkey<Government> constructors. Only the Government class has friend access to those private constructors. Note that both the default and copy constructors in Passkey are required in the example above.

The above implementation of Passkey requires C++11 because of the templated "friend T" declaration. To achieve a similar generic Passkey class in C++03 there exists a workaround that uses macros. For this and more information on applying the Passkey idiom to friend functions, I encourage you to read the following in-depth discussion on StackOverflow:

http://stackoverflow.com/questions/3324898/can-we-increase-the-re-usabil...

After reading this article, I hope you won't feel too guilty the next time you have a "good" reason to break class encapsulation because now you can do it in a controlled fashion and make peace with the friend declaration.