あなたがすでに認識していることを控えめに言っても少し難解です。あなたが何をしているのか、これらのヘルパークラスがどこで実装されているのか疑問に思ってコードに最初に遭遇したとき、私はちょっと頭を掻くかもしれません/ habits(この時点で完全に慣れる可能性があります)。
ヘッダーの情報量を減らしているのが好きです。特に、非常に大きなコードベースでは、コンパイル時の依存関係を減らし、最終的にビルド時間を短縮する実用的な効果があります。
私の直感的な反応は、このように実装の詳細を隠す必要があると感じた場合、ソースファイル内の内部リンケージを持つ独立した関数にパラメータを渡すことを支持することです。通常、特定のクラスを実装するのに役立つユーティリティ関数(またはクラス全体)をクラスのすべての内部にアクセスせずに実装し、代わりにメソッドの実装から関数(またはコンストラクター)に関連するものを渡すことができます。そして当然、それはあなたのクラスと「ヘルパー」の間のカップリングを減らすというボーナスがあります。また、複数のクラス実装に適用できる、より一般化された目的に役立つようになっていることがわかった場合、「ヘルパー」である可能性のあるものをさらに一般化する傾向があります。
また、コード内に多くの「ヘルパー」が表示されると、少ししびれます。常に真実とは限りませんが、コードの重複を排除するために関数を単に自由に分解する開発者の兆候となる場合があります。他のいくつかの機能を実装するために必要なコード。クラスの実装をより多くの関数に分解する方法に関して、少しだけ少し前もって考えると、より明確になり、内部に完全にアクセスしてオブジェクトのインスタンス全体を渡すのに特定のパラメーターを渡す方が有利になる場合がありますデザイン思考のそのスタイルを促進します。もちろん、あなたがそれをしていることを提案しているわけではありません(私にはわかりません)。
それが扱いにくい場合、私は2番目の、より慣用的な解決策であるピンプルを検討します(あなたはそれに関する問題に言及したことを理解していますが、最小限の労力でそれらを避けるために解決策を一般化できると思います)。これにより、クラスの実装に必要な多くの情報を、プライベートデータを含め、ヘッダーホールセールから遠ざけることができます。本格的なユーザー定義コピークターを実装することなく、値のセマンティクスを保持しながら、ピンリストのパフォーマンスの問題を、フリーリストのようなダート安い定時間アロケーター*で大幅に軽減できます。
- パフォーマンスの面では、Pimplは少なくともポインターのオーバーヘッドを発生させますが、実際の懸念が生じる場合はかなり深刻なケースになると思います。アロケーターによって空間的な局所性が大幅に低下しない場合、オブジェクトを反復するタイトなループ(パフォーマンスがそれほど重要な場合は一般的に均一である必要があります)は、実際にキャッシュミスを最小限に抑える傾向があります。ピンプルを割り当てるための空きリスト。クラスのフィールドをほぼ連続したメモリブロックに配置します。
個人的には、これらの可能性を使い果たした後にのみ、このようなことを検討します。代替手段がヘッダーに公開されるよりプライベートなメソッドのようなものであり、おそらくその秘密の性質だけが実際的な懸念である場合、それはまともなアイデアだと思います。
代替案
友達がいなくてもあなたの同じ目的をほぼ達成している私の頭に浮かんだ1つの選択肢は次のようなものです:
struct PredicateListData
{
int somePrivateField;
};
class PredicateList
{
PredicateListData data;
public:
bool match() const;
};
// In source file:
static bool fullMatch(const PredicateListData& p)
{
// Can access p.somePrivateField here.
}
bool PredicateList::match() const
{
return fullMatch(data);
}
今ではそれは非常に意味のない違いのように見えるかもしれませんが、私はそれを「ヘルパー」と呼びます(クラスの内部状態全体を必要とするかどうかに関係なく関数に渡しているため、おそらく軽de的な意味で)が発生する「衝撃」要因を回避することを除いてfriend
。一般friend
に、クラス内部は他の場所からアクセス可能であると言われているため、頻繁にさらなる検査がないことを見ると少し怖いようです(これは、独自の不変式を維持できない可能性があるという意味を持ちます)。あなたが使用しfriend
ている方法では、人々が練習を知っている場合、それはむしろ意味がありませんfriend
クラスのプライベート機能を実装するのに役立つ同じソースファイルに常駐していますが、上記の方法は、少なくともその種を回避する友人が関与しないという議論の余地のある利点でほぼ同じ効果を達成します( "Oh撮影、このクラスには友人がいます。プライベートはどこでアクセス/変更されますか?)。一方、すぐ上のバージョンは、の実装で行われたもの以外ではプライベートにアクセス/変更する方法がないことをすぐに伝えますPredicateList
。
それはおそらく、このレベルのニュアンスを持つやや独断的な領域に向かって進んでいます。なぜなら*Helper*
、クラスのプライベート実装の一部としてすべてがバンドルされている同じソースファイルにすべてを統一して名前を付ければ、誰でもすぐに理解できるからです。しかし、私たちがきちんとしたものを取得した場合、すぐ上のスタイルではfriend
、少し怖いように見える傾向のあるキーワードがなければ一見しただけで大した反応が起こらないかもしれません。
その他の質問:
コンシューマは、独自のクラスPredicateList_HelperFunctionsを定義し、プライベートフィールドにアクセスできるようにすることができます。私はこれを大きな問題とは見ていませんが(プライベートフィールドで本当に必要な場合は、キャストすることができます)、おそらく消費者がそのように使用することをお勧めしますか?
これは、クライアントが同じ名前の2番目のクラスを定義し、リンケージエラーなしで内部にその方法でアクセスできるAPI境界を越えた可能性があります。繰り返しますが、私は主にグラフィックスで作業するCコーダーで、このレベルの「what if」の安全性の懸念は優先順位リストで非常に低いため、これらのような懸念は手を振ってダンスをする傾向があるだけです存在しないふりをしてください。:-Dしかし、このような懸念がかなり深刻なドメインで作業している場合、それは適切な検討事項だと思います。
上記の代替案は、この問題に悩まされることも避けます。それでも使用したい場合はfriend
、ヘルパーをネストされたプライベートクラスにすることで、この問題を回避することもできます。
class PredicateList
{
...
// Declare nested class.
class Helper;
// Make it a friend.
friend class Helper;
public:
...
};
// In source file:
class PredicateList::Helper
{
...
};
これは有名なデザインパターンですか?
私の知る限りではありません。実装の詳細とスタイルの細かな点に本当に入り込んでいるので、ある種のものがあるのではないかと疑っています。
「ヘルパー地獄」
私は、多くの「ヘルパー」コードを使用した実装を見るときに時々縮むことについて、さらに明確化するように要求を受けました。それは、いくつかと少し議論の余地があるかもしれません。 「ヘルパー」の負荷を見つけるためだけに同僚がクラスを実装します。:-Dそして、これらすべてのヘルパーが正確に何をすべきかを理解しようとして頭をかきむしったチームは私だけではありませんでした。また、「ヘルパーを使わないでください」のような独断的な態度を取りたくはありませんが、実用的なときにそれらのないものを実装する方法を考えるのに役立つかもしれないという小さな提案をします。
すべてのプライベートメンバー関数は定義上ヘルパー関数ではありませんか?
そして、はい、私はプライベートメソッドを含めています。私は簡単なパブリックインターフェイスなどではなく、多少のような目的で不明確されているプライベートメソッドの無限のセットのようにクラスが表示された場合find_impl
やfind_detail
、またはfind_helper
、その後、私はまた、同様の方法でうんざり。
私が代替案として提案しているのはstatic
、「他の実装を支援する関数」よりも少なくともより一般化された目的でクラスを実装するのに役立つ内部宣言(宣言または匿名名前空間内)を持つ非メンバー非フレンド関数です。そして、それが一般的なSEの観点から好ましい理由については、ここでC ++「コーディング標準」からHerb Sutterを引用することができます:
会費を避ける:可能な場合は、非会員以外の行事を行うことをお勧めします。[...]非メンバーの非フレンド関数は、依存関係を最小化することでカプセル化を改善します。関数の本体は、クラスの非パブリックメンバーに依存することはできません(項目11を参照)。また、モノリシッククラスを分解して分離可能な機能を解放し、結合をさらに減らします(項目33を参照)。
また、可変範囲を狭めるという基本原則の観点から、彼がある程度話している「会費」を理解することもできます。最も極端な例として、プログラム全体を実行するために必要なすべてのコードを備えた神オブジェクトを想像すると、すべての内部にアクセスできるこの種の「ヘルパー」(関数、メンバー関数または友人)クラスのprivates)は、基本的にこれらの変数をグローバル変数と同様に問題なくレンダリングします。この最も極端な例では、状態とスレッドの安全性を管理し、グローバル変数で得られる不変条件を維持するという困難がすべてあります。そしてもちろん、ほとんどの実際の例はこの極端に近い場所ではないことを願っていますが、情報の隠蔽はアクセスされる情報の範囲を制限するのと同じくらい便利です。
さて、サッターはすでにここで良い説明をしていますが、機能を設計する方法に関して、デカップリングは心理的な改善のように促進する傾向があります(少なくとも脳が私のように機能する場合)。クラスのすべてにアクセスできない関数を設計し始めるとき、それを渡す関連パラメーターのみ、またはクラスのインスタンスをパラメーターとして、そのパブリックメンバーのみを渡す場合、それは好む設計マインドセットを促進する傾向があります分離に加えて、カプセル化の改善を促進することに加えて、すべてにアクセスできる場合に設計するように誘惑されるかもしれないものよりも明確な目的を持つ関数。
極限に話を戻すと、グローバル変数にあふれたコードベースは、明確で一般化された目的で機能を設計するように開発者を正確に誘惑しません。関数でより多くの情報にアクセスできるようになると、より多くの人間がそれを非一般化し、その関数のより具体的で関連性のあるパラメーターを受け入れる代わりに、このすべての追加情報にアクセスするためにその明確さを減らす誘惑に直面します国家へのアクセスを制限し、適用範囲を広げ、意図の明確さを改善する。それはメンバー関数または友人に(一般にある程度ではありますが)適用されます。