![](https://habrastorage.org/files/0d1/bb0/a84/0d1bb0a845a84e4d8f716ccfc88c9cfb.jpg)
したがって、タスクは次のとおりです。サーバー、クライアント、および侵入者のクラスがあります。 クライアントは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(); // <- } };
- lawyerクラスのプロキシメソッドはインラインである必要があり、その後、オプティマイザーはそれらを削除し、CardAccountクラスのメソッドを直接呼び出します。 コードをgodboltにコピーし、バリアント用に生成されたコードをproxy_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パラメーターを構築できる各「独自の」パブリックメソッドのみを再び呼び出すことができます。 サーバーの実装の詳細は、「エイリアン」メソッドと同様に完全にアクセスできません。
- このバージョンでは、プロキシ関数もインラインで、呼び出しをさらにプロキシする必要があります。この場合(オプティマイザーが動作した後)、一時的なPasskey <>オブジェクトは作成されず、オーバーヘッドはゼロになります。
- パスキー<>をデフォルトの引数にすることはできません。 このオプションは機能しません:
class Server { public: void proxy_some_method( Passkey<Client> pass = Passkey<Client>() ); private: void some_method(); };
- わかりやすくするために、教育目的でのみproxy_プレフィックスを使用してプロキシメソッドを呼び出しました。
おわりに
説明されているAttorney-ClientおよびPasskeyのイディオムにより、クラスのプライベートメソッドへのアクセスを選択的に提供できます。 これらのメソッドは両方ともランタイムオーバーヘッドなしで機能しますが、追加のコードを記述する必要があり、friendキーワードを使用するよりもクラスインターフェースを目立たなくします。 プロジェクトでこの庭全体をフェンスで囲む必要があるか、それとも価値がないかは、あなた次第です。