静的メンバー以外のユーティリティクラスはC ++のアンチパターンですか?


39

クラスに関係のない関数をどこに置くべきかという質問、C ++でクラス内のユーティリティ関数を結合するのが理にかなっているのか、それとも名前空間にフリー関数として存在するのかという議論を巻き起こしました。

私は、後者のオプションが存在しないC#のバックグラウンドから来ているため、私が書いている小さなC ++コードで静的クラスを使用する傾向があります。しかし、その質問に対する最高の投票された回答といくつかのコメントは、静的なクラスがアンチパターンであることを示唆していても、無料の関数が優先されるべきであると述べています。C ++ではなぜそうなのですか?少なくとも表面的には、クラスの静的メソッドは、名前空間の自由な関数と見分けがつかないように見えます。なぜこのように後者を好むのですか?

ユーティリティ関数のコレクションが何らかの共有データを必要とする場合、たとえば、プライベート静的フィールドに保存できるキャッシュなど、状況は異なりますか?


「機能分解」アンチパターンに少し似ています。
-user281377

7
簡単な答え:これらの関数をラップするためにクラスは必要ありません。無料の関数は、疑似OO構造にクランチするよりも、タスクにはるかに適しています。これは、「純粋なOO」言語でのみ必要な回避策です。
クリスは、モニカを復活させる

回答:


39

クラスと名前空間の両方の意図を比較する必要があると答えると思います。ウィキペディアによると:

クラス

オブジェクト指向プログラミングでは、クラスは、クラスインスタンス、クラスオブジェクト、インスタンスオブジェクト、または単にオブジェクトと呼ばれる自身のインスタンスを作成するための青写真として使用される構造です。クラスは、これらのクラスインスタンスに状態と動作を持たせる構成メンバーを定義します。データフィールドメンバー(メンバー変数またはインスタンス変数)により、クラスオブジェクトは状態を維持できます。他の種類のメンバー、特にメソッドは、クラスオブジェクトの動作を可能にします。クラスインスタンスは、関連するクラスのタイプです。

名前空間

一般に、名前空間は、保持する識別子(名前、技術用語、または単語)のコンテキストを提供するコンテナであり、異なる名前空間に存在する同音異義語識別子の明確化を可能にします。

さて、関数をクラス(静的)または名前空間に入れることで何を達成しようとしていますか?名前空間の定義があなたの意図をより良く説明することを望みます-あなたが望むのはあなたの関数のコンテナです。クラス定義で説明されている機能は必要ありません。クラス定義の最初の単語は「オブジェクト指向プログラミング」であることに注意してください。ただし、関数のコレクションに関してオブジェクト指向はありません。

おそらく技術的な理由もありますが、Javaから来てC ++であるマルチパラダイム言語に頭を悩ませようとしている人として、最も明白な答えは次のとおりです。


1
「これを達成するためにオブジェクト指向は必要ありません」の+1
Ixrec

私はこれをオブジェクト指向のファンとしては同意しますが、ネストされた名前空間を内部で宣言できないため、名前空間を置き換えるなど、All-staticクラスを使用することが唯一のソリューションである状況がいくつかあると思いますクラス。
ワエルブーグライ

26

私はそれをアンチパターンと呼ぶことに非常に慎重になるでしょう。通常、名前空間が好まれますが、名前空間テンプレートがなく、名前空間をテンプレートパラメータとして渡すことができないため、静的メンバー以外のクラスを使用することは非常に一般的です。


3
他の答えは、OPの最後の行で明らかにしました。名前空間はほとんど名前付きスコープなので、アクセス制御が必要な場合は、クラスのみが利用可能なツールです。
cmannett85

2
@ cbamber85は公平であるため、最初の2つの答えを読んだにだけその行を入れました。
PersonalNexus

@PersonalNexusああ、まあまあ、私は気づかなかった!
cmannett85

10
@ cbamber85:それは完全に真実ではありません。実装ファイルに「プライベート」関数を配置することで、さらに優れたカプセル化が得られるので、ユーザーはプライベート宣言すら見ることができません。
-UncleBens

2
+1テンプレートは確かに、名前空間にまだ機能が欠けている点です。
クリスは、モニカを復活させる

13

昔、FORTRANクラスを受講する必要がありました。その時までに他の命令型言語を知っていたので、あまり勉強しなくてもFORTRANができると思いました。私が最初の宿題を提出したとき、教授は私にそれを返し、それをやり直すように頼みました。彼は、FORTRAN構文で書かれたPascalプログラムは有効な提出としてカウントされないと言いました。

ここでも同様の問題があります。C++でユーティリティクラスをホストするために静的クラスを使用することは、C ++の異質なイディオムです。

質問で述べたように、C#でユーティリティ関数に静的クラスを使用することは必要です。独立した関数はC#のオプションではありません。プログラマーが他の方法で、つまり静的ユーティリティクラス内で、独立した関数を定義できるようにするためのパターンを開発するために必要な言語。これは、単語ごとに取ったJavaのトリックでした。たとえば、.NETのjava.lang.MathSystem.Mathはほぼ同形です。

ただし、C ++は、同じ目標を達成するための別の機能である名前空間を提供し、標準ライブラリの実装で積極的に使用しています。静的クラスの余分なレイヤーを追加することは不要であるだけでなく、C#やJavaのバックグラウンドを持たない読者にとっては直感に反するものです。ある意味では、ネイティブに表現できる何かのために言語に「ローン翻訳」を導入しています。

関数でデータを共有する必要がある場合、状況は異なります。関数はもはや無関係ではないため、Singletonパターンはこの要件に対処するための優先される方法になります。


6
+1、シングルトンの推奨を除く。彼らはされていない C ++に好適!関数は、状態を共有する必要がある場合はクラスに含まれることがありますが、本当に必要な場合を除き、クラスはシングルトンであってはなりません。そして、彼らは通常そうではありません。
Maxpm

@Maxpm誤解しないでください。シングルトンはグローバルな静的変数にのみ優先されます。それ以外には、それについて「好ましい」ものは何もありません。
-dasblinkenlight

2
この優れた記事「シングルトン:1995年以来あなたが知らなかった問題を解決する」があります。要約すると、よく知られているインスタンスをクラス自体に結合すると、柔軟性が不必要に制限され、何も得られないということです。ほとんどの場合、クラスがあり、どこかに共有インスタンスがありますが、シングルトンにしないでください。
ジャン・ヒューデック

「外国語のイディオム」が正しい用語かどうかはわかりません。それは非常に一般的なイディオムです。私はかなりの数のプログラミングチームは... Stroustrup氏のE2を使用していると思う
ニックキースリー

10

おそらく、なぜあなたはすべて静的なクラスが必要なのか尋ねる必要がありますか?

私が考えることができる唯一の理由は、「すべてがクラス」である他の言語(JavaおよびC#)がそれらを必要とすることです。これらの言語はトップレベルの関数をまったく作成できないため、それらを保持するためのトリックを発明しました。それが静的メンバー関数でした。少しの回避策ですが、C ++にはそのようなものは必要ありません。新しいトップレベルの独立した関数を直接作成できます。

特定のデータ項目で動作する関数が必要な場合、それらをデータを保持するクラスにバンドルすることは理にかなっていますが、これらは関数ではなく、クラスのデータで動作するそのクラスのメンバーになり始めます。

特定のデータ型で動作しない関数がある場合(クラスは新しいデータ型を定義する方法であるため、ここで単語を使用します)、それ以外の場合は、クラスにそれらを押し込むのは本当にアンチパターンですセマンティック目的。


10
確かに、Javaの「すべての静的メンバーを持つクラス」は、実際にはJavaの制限を回避するためのハックと言えます。
チャールズサルビア

@CharlesSalvia:私は丁寧でした:)私もmain()がjavaクラスの一部であることについて同じ気持ちがありますが、なぜそうしたのかは理解しています。
gbjbaanb

これは、実際には、すべて静的なクラスが必要な理由に対する答えを提供しているようです。それとも私はあなたを誤解していますか?たとえば、1つのクラス(ROMに格納する予定)によって読み取られ、別のクラス(RAMに格納される)によって書き込まれる値を変更できる変数を保持する必要があります。単に既知のラムの位置に配置するだけでは十分ではありません。クラス内でそれをラップすることで(およびそのアクセッサも)アクセス制御を微調整できます。2番目の段落で説明する内容のほとんど。
iheanyi 14年

5

少なくとも表面的には、クラスの静的メソッドは、名前空間の自由な関数と見分けがつかないように見えます。

言い換えれば、名前空間の代わりにクラスを持つことには利点がありません。

なぜこのように後者を好むのですか?

一つにはstatic、常に入力する手間を省くことができますが、それは間違いなく小さな利点です。

主な利点は、それが仕事にとって最も強力なツールではないということです。クラスはオブジェクトの作成に使用でき、変数のタイプまたはテンプレート引数として使用できます。これらはどちらも、関数のコレクションに必要な機能ではありません。したがって、クラスが誤って誤用されないように、これらの機能を持たないツールを使用することをお勧めします。

それに続いて、名前空間を使用すると、コードのユーザーに対して、これが関数のコレクションであり、オブジェクトを作成するための設計図ではないことがすぐに明らかになります。


5

...しかし、いくつかのコメントは、静的関数がアンチパターンであることを示唆していても、無料の関数が優先されるべきだと言っています。C ++ではなぜそうなのですか?少なくとも表面的には、クラスの静的メソッドは、名前空間の自由な関数と見分けがつかないように見えます。なぜこのように後者を好むのですか?

完全に静的なクラスで仕事は完了しますが、4ドアセダンがやるときはチップとサルサのために53フィートのセミトラックを食料品店に運転するようなものです(つまり、やり過ぎです)。クラスにはわずかな追加オーバーヘッドがあり、クラスが存在することで、インスタンス化するのが良い考えであるという印象を誰かに与えるかもしれません。C ++(およびすべての関数が無料であるC)が提供する無料の関数は、それを行いません。それらは単なる機能であり、他には何もありません。

ユーティリティ関数のコレクションが何らかの共有データを必要とする場合、たとえば、プライベート静的フィールドに保存できるキャッシュなど、状況は異なりますか?

あんまり。staticC(およびC ++以降)の本来の目的は、あらゆるレベルのスコープで使用可能な永続ストレージを提供することでした。

int counter() {
    static int value = 0;
    value++;
}

スコープをファイルのレベルまで取り出すことができます。これにより、すべてのより狭いスコープから見えるようになりますが、ファイルの外側からは見えません。

static int value = 0;

int up() { value++; }
int down() { value--; }

C ++のプライベートスタティッククラスメンバーも同じ目的を果たしますが、クラスのスコープに制限されます。counter()上記の例で使用されている手法はC ++メソッド内でも機能し、変数がクラス全体に表示される必要がない場合は実際に行うことをお勧めします。


データを共有しない無関係なユーティリティ関数のグループは、名前空間でより適切にグループ化されるべきだと思います。しかし、共有データへのアクセスとおそらくアクセス制御を必要とする密結合ユーティリティ関数のグループがある場合、静的クラスが最良の選択です。関数内の静的はリエントラントではありません。モジュールのグローバルを使用する...このコンテキストではわずかに優れていますが、説明した要件がある場合は、すべて静的クラスが最適なソリューションであると考えています。
iheanyi

データを共有する場合、静的メソッドを持たないクラスとして優れている可能性がありますか
ニックケイリー

@NickKeighleyそれは、1つのインスタンスを作成してそれを必要なものすべてに渡すか、単にクラス内で静的にするか、という議論の勝者によって異なります。
Blrfl

1

関数が状態を保持せず、リエントラントである場合、クラス内でそれを押し出すことはあまり意味がありません(言語によって強制されない限り)。関数が何らかの状態を維持している場合(たとえば、静的ミューテックスによってのみスレッドセーフにすることができる場合)、クラスの静的メソッドが適切であると思われます。


1

作ることをC ++について非常に基本的なものがありますが、ここでのトピックに関する言説の多くは、理にかなっているnamespacesclassES / 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);

新しいアロケーターが互換性のあるallocateand release関数を公開している限り、新しいアロケーターへの切り替えは簡単です。

これは名前空間では実現できません。

クラスの一部となる関数は常に必要ですか?いや

静的クラスを使用するのはアンチパターンですか?コンテキストに依存します。

ユーティリティ関数のコレクションが何らかの共有データを必要とする場合、たとえば、プライベート静的フィールドに保存できるキャッシュなど、状況は異なりますか?

その場合、あなたが達成しようとしていることは、オブジェクト指向プログラミングによって最もよく提供される可能性が高いです。


クラス定義で定義されたメソッドは暗黙的にインラインであるため、inlineキーワードの使用はここでは冗長です。en.cppreference.com/w/cpp/language/inlineを参照してください。
トレバー

1

ジョン・カーマックが有名に言ったように:

「場合によっては、エレガントな実装は単なる関数です。メソッドではありません。クラスではなく、フレームワークではありません。単なる関数です。」

私はそれをほとんどまとめていると思います。明らかにクラスではないのに、なぜ強制的にクラスにするのですか?C ++には、実際に関数を使用できるという贅沢さがあります。Javaでは、すべてがメソッドです。次に、ユーティリティクラスとそれらの多くが必要です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.