クラスメソッドへの選択的アクセスのためのイディオムの弁護士-クライアントとパスキー

C ++でアプリケーションを設計する場合、クラスのプライベートメソッドへのアクセスを別のクラスまたはフリー関数に提供することが必要になる場合があります。 これを行うために、C ++にはfriendキーワードがあり、クラスのパブリックインターフェイスだけでなく、プライベートおよびすべての実装の詳細へのフルアクセスも提供します。 したがって、友人は「すべてまたは無」の原則に基づいて仕事をし、「すべて」は多すぎるかもしれません。 たとえば、Facadeクラスと複数のクライアントClient1、Client2がある場合、各クライアントに特定のメソッドセットへのアクセスのみを提供する必要があり、各クライアントは実装の詳細へのアクセスを提供せずに独自のセットを持つ必要があります。 C ++でこの問題を解決するには、すべての可能性があります。 この記事では、Attorney-ClientとPasskeyの2つのイディオムと、それらをゼロオーバーヘッドで使用する方法について説明します。



したがって、タスクは次のとおりです。サーバー、クライアント、および侵入者のクラスがあります。 クライアントはServer :: some_method()にアクセスする必要がありますが、実装の詳細にはアクセスできません。 同時に、侵入者はサーバーにアクセスできません。



class Server { private: //   void some_method(); //   Client void one_more_method(); //      private: //    ... }; class Client; class Intruder;
      
      





弁護士クライアント



弁護士と依頼人のイディオムはよりシンプルで簡単ですが、長いです-それから始めましょう。 クライアントに必要なアクセスを提供するには、単にサーバーの友達にすることはできません(サーバーのすべてのコンテンツへのアクセスを取得します)。また、必要なメソッドを公開することもできません(クラッカーもアクセスできます)。 この状況では、信頼できる仲介者が救助、またはむしろ弁護士になります。



 class Attorney;
      
      





信頼チェーンは次のように編成されます。クライアントは弁護士の友人になり、それが別のサーバーになります。 Attorneyクラスには、サーバーへのリクエストをプロキシするプライベートインライン静的メソッドがあります。







 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(); // <-    } };
      
      







パスキー



プライベートインターフェイスへの選択的アクセスを提供する2番目の方法は、Passkeyイディオムです。 それはより短く、コードはよりきれいです。したがって、私はそれがより好きですが、もう少し明白ではありません。 タスクは同じです:サーバー、クライアント、侵入者、ただし今回はプロキシメソッドがパブリックとして宣言されていますが、プライベートコンストラクターを持つ特別なPasskeyパラメーターがそれらに追加され、明示的にリストされたフレンド(クラス、フリー関数)によってのみ呼び出すことができます。 Passkeyパラメータはユーティリティであり、プロキシ関数の呼び出し時にすぐに作成され、終了すると破棄されます(これは一時オブジェクトであり、変数に保存されません)。 その結果、void some_method(Passkey)は、Passkeyコンストラクターを呼び出すことができるクラスのみを呼び出すことができます(これらのクラスはすべて、Passkeyフレンドとしてリストされています)。







 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(); // <-    } };
      
      





読みやすさを向上させ、Passkeyクラスコードを他のクラスに含めることの重複を取り除くために、定型化して別のヘッダーファイルに入れることができます。







 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; };
      
      





Passkeyの唯一の目的は、一時インスタンスを作成してプロキシメソッドに渡すことです。このため、空のデフォルトコンストラクターと移動が必要です。他のすべてのコンストラクターと代入演算子は禁止されています(念のため、他の目的でPasskeyを使用しないため)。







最終版
 // === 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( {} ); //  ... } };
      
      







呼び出し元クラス(クライアント、スーパークライアント)は、Passkeyパラメーターを構築できる各「独自の」パブリックメソッドのみを再び呼び出すことができます。 サーバーの実装の詳細は、「エイリアン」メソッドと同様に完全にアクセスできません。









 class Server { public: void proxy_some_method( Passkey<Client> pass = Passkey<Client>() ); private: void some_method(); };
      
      







おわりに



説明されているAttorney-ClientおよびPasskeyのイディオムにより、クラスのプライベートメソッドへのアクセスを選択的に提供できます。 これらのメソッドは両方ともランタイムオーバーヘッドなしで機能しますが、追加のコードを記述する必要があり、friendキーワードを使用するよりもクラスインターフェースを目立たなくします。 プロジェクトでこの庭全体をフェンスで囲む必要があるか、それとも価値がないかは、あなた次第です。








All Articles