質問 クラスに関連しない関数をどこに置くべきか は、C++でクラス内のユーティリティ関数を組み合わせるのが意味があるのか、それとも名前空間にフリー関数として存在させるのが理にかなっているのかについて議論を巻き起こしました。
私は後者のオプションが存在しないC#のバックグラウンドに由来しているため、私が書く小さなC++コードで静的クラスを使用する傾向にあります。しかし、その質問に対する最高投票の回答といくつかのコメントは、静的クラスがアンチパターンであることを示唆しているとしても、フリー関数が優先されると述べています。なぜC++ではそうなのですか?少なくとも表面上は、クラスの静的メソッドは名前空間のフリー関数と区別がつかないようです。なぜ後者を好むのですか?
ユーティリティ関数のコレクションに共有データが必要な場合、状況は異なります。プライベート静的フィールドに保存できるキャッシュ?
クラスと名前空間の両方の意図を比較する必要があると答えると思います。ウィキペディアによると:
オブジェクト指向プログラミングでは、クラスはそれ自体のインスタンスを作成するための青写真として使用される構造体です。クラスインスタンス、クラスオブジェクト、インスタンスオブジェクト、または単にオブジェクトと呼ばれます。クラスは、これらのクラスインスタンスが状態と動作を持つことを可能にする構成メンバーを定義します。データフィールドメンバー(メンバー変数またはインスタンス変数)は、クラスオブジェクトが状態を維持できるようにします。他の種類のメンバー、特にメソッドは、クラスオブジェクトの動作を可能にします。クラスインスタンスは、関連するクラスのタイプです。
一般に、名前空間は、それが保持する識別子(名前、技術用語、または単語)のコンテキストを提供するコンテナであり、異なる名前空間に存在する同音異義語識別子の明確化を可能にします。
ここで、関数をクラス(静的)または名前空間に配置することによって何を達成しようとしていますか?名前空間の定義があなたの意図をよりよく説明していると思います-必要なのは関数のコンテナだけです。クラス定義で説明されている機能は必要ありません。クラス定義の最初の単語は "オブジェクト指向プログラミングの場合"ですが、関数のコレクションについてはオブジェクト指向はありません。
おそらく技術的な理由もありますが、Java=から来て、C++であるマルチパラダイム言語に頭を悩ませようとしている人として、私にとって最も明白な答えは次のとおりです。これを実現するにはOOが必要です。
私はそれをアンチパターンと呼ぶことに非常に慎重になるでしょう。名前空間が通常推奨されますが、名前空間テンプレートがなく、名前空間をテンプレートパラメーターとして渡すことができないため、静的メンバーのみのクラスを使用するのが一般的です。
昔、FORTRANクラスを受講する必要がありました。その時までに他の必須言語を知っていたので、あまり勉強しなくてもFORTRANを実行できると思いました。私が最初の宿題を提出したとき、教授は私にそれを返し、それをやり直すように頼みました:彼はFORTRAN構文で書かれたPascalプログラムは有効な提出として数えられないと言った。
C++でホストユーティリティ関数に静的クラスを使用することは、C++にとって異質な慣習です。
あなたの質問で述べたように、C#でユーティリティ関数に静的クラスを使用することは、必要なことの問題です:独立した関数は、C#のオプションではありません。プログラマーが他の方法で、つまり静的ユーティリティクラス内で独立関数を定義できるようにするためのパターンを開発するために必要な言語。これはJava単語ごとに行われたトリック:たとえば、.NETの Java.lang.Math および System.Math など)ほぼ同型です。
ただし、C++は、同じ目標を達成するための別の機能である名前空間を提供し、標準ライブラリの実装で積極的に使用します。静的クラスの追加レイヤーを追加することは不必要なだけでなく、C#またはJavaバックグラウンドなしのリーダーに直観に反しています。ある意味で、何かのための "ローン翻訳"を言語に導入していますそれはネイティブに表現できます。
関数がデータを共有する必要がある場合、状況は異なります。関数はもはや無関係ではないため、- シングルトンパターン がこの要件に対処するための推奨される方法になります。
おそらく、すべて静的なクラスが必要な理由を尋ねる必要があるでしょうか?
私が考えることができる唯一の理由は、非常に「すべてがクラスである」他の言語(JavaおよびC#)がそれらを必要とすることです。これらの言語はトップレベルの関数をまったく作成できないため、それらを維持するためのトリックが考案され、それが静的メンバー関数でした。これらは少しの回避策ですが、C++ではそのようなことは必要ありません。新しいトップレベルの独立した関数を直接作成できます。
特定のデータ項目を操作する関数が必要な場合、それらをデータを保持するクラスにバンドルすることは理にかなっていますが、これらは関数ではなく、クラスのデータを操作するそのクラスのメンバーになることを開始します。
特定のデータ型を操作しない関数がある場合(クラスは新しいデータ型を定義する方法であるため、ここではWordを使用しています)、実際には、それらをクラスに押し込むのはアンチパターンです。セマンティックな目的。
少なくとも表面上は、クラスの静的メソッドは名前空間のフリー関数と区別がつかないようです。
言い換えれば、名前空間の代わりにクラスを持つことには利点がありません。
なぜ後者を好むのですか?
1つには、常にstatic
と入力する手間を省くことができますが、それは間違いなくかなり小さな利点です。
主な利点は、それが仕事にとって最も強力でないツールであることです。クラスはオブジェクトの作成に使用でき、変数のタイプまたはテンプレート引数として使用できます。これらはどちらも、関数のコレクションに必要な機能ではありません。したがって、これらの機能を持たないツールを使用して、クラスが誤って誤用されないようにすることをお勧めします。
名前空間を使用すると、コードのすべてのユーザーにとって、これが関数のコレクションであり、オブジェクトを作成するための青写真ではないことがすぐにわかります。
...しかし、いくつかのコメントは、静的クラスがアンチパターンであると示唆している場合でも、フリー関数が優先されると述べています。なぜC++ではそうなのですか?少なくとも表面上は、クラスの静的メソッドは名前空間のフリー関数と区別がつかないようです。なぜ後者を好むのですか?
すべて静的なクラスは仕事を完了しますが、それは4ドアセダンが行うときにチップとサルサのために食料品店に53フィートのセミトラックを運転するようなものです(つまり、それはやりすぎです)。クラスには追加のオーバーヘッドが少しありますが、クラスが存在することで、クラスをインスタンス化することは良いアイデアであると思われるかもしれません。 C++(およびすべての関数が無料であるC)によって提供される無料の関数はそれを行いません。それらは単なる機能であり、他には何もありません。
ユーティリティ関数のコレクションに共有データが必要な場合、状況は異なります。プライベート静的フィールドに格納できるキャッシュ?
あんまり。 C(およびそれ以降のC++)でのstatic
の本来の目的は、スコープのどのレベルでも使用できる永続的なストレージを提供することでした。
_int counter() {
static int value = 0;
value++;
}
_
スコープをファイルのレベルに移動できます。これにより、すべてのより狭いスコープから見えるようになりますが、ファイルの外からは見えません。
_static int value = 0;
int up() { value++; }
int down() { value--; }
_
C++のプライベート静的クラスメンバーは同じ目的を果たしますが、クラスのスコープに限定されます。上記のcounter()
の例で使用されている手法は、C++メソッドの内部でも機能し、変数がクラス全体から見える必要がない場合は、実際にお勧めします。
関数が状態を保持せず、再入可能である場合、(言語によって強制されない限り)クラス内でその関数を強制することはあまり意味がないようです。関数が何らかの状態を維持している場合(たとえば、静的ミューテックスによってのみスレッドセーフにすることができる場合)、クラスの静的メソッドが適切であると思われます。
C++には、namespaces
とclass
es/struct
sを大きく異なるものにする非常に基本的なものがありますが、ここでのトピックに関する多くの談話は理にかなっています。
静的クラス(すべてのメンバーが静的であり、クラスがインスタンス化されることのないクラス)はそれ自体がオブジェクトであり、関数を含む単なるnamespace
ではありません。
テンプレートのメタプログラミングにより、静的クラスをコンパイル時オブジェクトとして使用できます。
このことを考慮:
template<typename allocator_type> class allocator
{
public:
inline static void* allocate(size_t size)
{
return allocator_type::template allocate(size);
}
inline static void release(void* p)
{
allocator_type::template release(p);
}
};
これを使用するには、クラス内に含まれる関数が必要です。名前空間はここでは機能しません。考慮してください:
class mallocator
{
inline static void* allocate(size_t size)
{
return std::malloc(size);
}
inline static void release(void* p)
{
return std::free(p);
}
};
それを使用するには:
using my_allocator = allocator<mallocator>;
void* p = my_allocator::allocate(1024);
...
my_allocator::release(p);
新しいアロケータが互換性のあるallocate
およびrelease
関数を公開している限り、新しいアロケータへの切り替えは簡単です。
これは名前空間では実現できません。
クラスの一部になるために常に関数が必要ですか?番号。
静的クラスの使用はアンチパターンですか?状況によります。
ユーティリティ関数のコレクションに共有データが必要な場合、状況は異なります。プライベート静的フィールドに格納できるキャッシュ?
その場合、あなたが達成しようとしていることは、おそらくオブジェクト指向プログラミングを通して最もよく提供されるでしょう。
ジョン・カーマックが有名に言ったように:
「エレガントな実装は、単なる関数であり、メソッドでもクラスでもフレームワークでもありません。単なる関数です。」
私はそれをかなり要約していると思います。明らかにクラスではないのに、なぜそれを強制的にクラスにするのですか? C++には、実際に関数を使用できるという贅沢があります。 Javaでは、すべてがメソッドです。次に、ユーティリティクラスとその多くが必要です。