Better Friendship : Passkey Idiom

Better Friendship : Passkey Idiom

20 Aug 2018    

Often when declaring a class as a friend, we give it more freedom than it actually needs i.e. it has access to all the private members, which in quite a few cases is not ideal. The Passkey Idiom is one way to mitigate this. Consider the following class:

class Symbol {
    std::string Name;
    int Id;
    RendererType type;
    
    Texture Texture;
public:
    int GetId() const;
    std::string const& GetName() const;    
};

Let’s say for example there is a Renderer class that wants to access the Texture object in the Symbol class without other classes being able to access it as well i.e. it cannot be a public function. To do this, we can declare the Renderer class as a friend.

class Renderer {
public:
    void RenderSymbol(Symbol* sym);
};

class Symbol {
friend Renderer;
// ...
};

This is problematic since now Renderer has access to Name, Id and Type as well. Instead we can create a Passkey that only allows the Renderer to access that specific data member.

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

class Symbol {
    // No friend declaration required here
public:
    Texture* GetTexture(Passkey<Renderer>) const;
};

// Definition of function declared above
void Renderer::RenderSymbol(Symbol* sym) {
    sym->GetTexture(Passkey<Renderer>{}); // Compiles
}

void some_other_function(Symbol* sym) {
    sym->GetTexture(Passkey<Renderer>{}); // Compiler Error
}

This only allows the renderer to acess the texture since it’s the only one with access to Passkey’s private constructor. This passkey header and class is available in the Common library for use. It is a very simply class that allows a private instance to be created by means of indirecting the “friendship” to a third party (Obligatory quote from David Wheeler).