When designing C ++ applications, it is sometimes necessary to provide access to private class methods to another class or free function. To do this, the C ++ language has the friend keyword, which provides full access not only to the class's public interface, but also to the private and all implementation details. Thus a friend works on the principle of “all or nothing” and “all” may be too much. For example, when there is a Facade class and several Clients Client1, Client2, it may be necessary to provide each client with access only to a specific set of methods, and each client to its own set, without providing access to implementation details. To solve this problem in C ++ there are all the possibilities. In this article I will discuss the two idioms of Attorney-Client and Passkey and how to use them with zero overhead.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