class Server { private: // void some_method(); // Client void one_more_method(); // private: // ... }; class Client; class Intruder;
class Attorney;
The chain of trust will be organized this way: Client will be Attorney's friend, and that Server's friend. The Attorney class will have a private inline static method that proxies requests to the Server.
class Server { private: // void some_method(); // Client void one_more_method(); // private: // ... friend class Attorney; }; class Attorney { private: static void proxy_some_method( Server& server ) { server.some_method(); } friend class Client; }; class Client { private: void do_something(Server& server); }; void Client::do_something( Server& server ) { // server.some_method(); // <- Attorney::proxy_some_method( server ); // server.one_more_method(); // <- } class Intruder { private: void do_some_evil_staff( Server& server ) { // server.some_method(); // <- } };
The second way to provide selective access to a private interface is the Passkey idiom. It is shorter and the code is cleaner, so I like it more, but a little more obscure. The task is the same: Server, Client, Intruder, but this time the proxy methods are declared public, but they add a special parameter Passkey with a closed constructor, which can only be called by explicitly listed friends (classes, free functions). The Passkey is a service parameter, it is created immediately when the proxy function is called, and it is destroyed when it exits (it is a temporary object, it is not stored in a variable). As a result, void some_method (Passkey) can be called only by the class that the Passkey constructor can call (and all these classes are listed in Passkey's friends).
class Server { public: class Passkey { private: friend class Client; // Client Passkey Passkey() noexcept {} Passkey( Passkey&& ) {} }; void some_method( Passkey ) // Passkey Client { some_method(); } private: // void some_method(); // Client void one_more_method(); // private: // ... }; class Client { private: void do_something( Server& server ); }; void Client::do_something( Server& server ) { // server.some_method(); // <- // server.one_more_method(); // <- server.some_method( Server::Passkey() ); // , server.some_method( {} ); } class Intruder { private: void do_some_evil_staff( Server& server ) { // server.some_method(); // <- } };
To improve readability and get rid of duplication of the inclusion of Passkey class code in other classes, it can be made template and put into a separate header file.
template <typename T> class Passkey { private: friend T; Passkey() noexcept {} Passkey( Passkey&& ) {} Passkey( const Passkey& ) = delete; Passkey& operator=( const Passkey& ) = delete; Passkey& operator=( Passkey&& ) = delete; };
The only purpose of Passkey is to create a temporary instance of it and pass it to the proxy method. This requires empty default constructor and displacements, all other constructors and assignment operators are prohibited (just in case, in order not to use Passkey for other purposes).
// === passkey.hpp template <typename T> class Passkey { private: friend T; Passkey() noexcept {} Passkey( Passkey&& ) {} Passkey( const Passkey& ) = delete; Passkey& operator=( const Passkey& ) = delete; Passkey& operator=( Passkey&& ) = delete; }; // === server.hpp class Client; class SuperClient; class Server { public: void proxy_some_method( Passkey<Client> ); // proxy Client void proxy_some_method( Passkey<SuperClient> ); // proxy SuperClient private: // void some_method(); // Client void one_more_method(); // private: // ... }; inline void Server::proxy_some_method( Passkey<Client> ) { some_method(); } inline void Server::proxy_some_method( Passkey<SuperClient> ) { some_method(); } // === client.hpp class Client { private: void do_something( Server& server ); }; void Client::do_something( Server& server ) { // server.some_method(); // <- // server.one_more_method(); // <- server.proxy_some_method( Passkey<Client>() ); // server.proxy_some_method( {} ); // <- } // evil.hpp class Intruder { private: void do_some_evil_staff( Server& server ) { // server.some_method(); // <- // server.proxy_some_method( Passkey<Client>() ); // // server.proxy_some_method( {} ); // ... } };
The calling classes (Client, SuperClient) will again be able to call only each “own” public methods, for which they will be able to construct the Passkey parameter. The details of the Server implementation are completely unavailable to them, as are the “alien” methods.
class Server { public: void proxy_some_method( Passkey<Client> pass = Passkey<Client>() ); private: void some_method(); };
The Attorney-Client and Passkey idioms described allow selective access to private class methods. Both of these methods work with zero runtime overhead, however, they require writing additional code and make the class interface not so obvious as compared to using the friend keyword. Whether it is necessary to fence all this garden in your project or it is not worth it - this is up to you.
Source: https://habr.com/ru/post/326540/
All Articles