質問
古いコードをリファクタリングしているときに、実際には静的であるべき多くのパブリック メソッドを削除しました。これらのメソッドは、a) メンバー データを操作せず、他のメンバー関数を呼び出さない、および b) 他の場所で役立つ可能性があるためです。
このことから、「ヘルパー」機能をグループ化する最善の方法について考えるようになりました。Java/C# の方法は、プライベート コンストラクターで静的関数のクラスを使用することです。例:
class Helper
{
private:
Helper() { }
public:
static int HelperFunc1();
static int HelperFunc2();
};
ただし、C++ であるため、名前空間を使用することもできます。
namespace Helper
{
int HelperFunc1();
int HelperFunc2();
}
ほとんどの場合、名前空間アプローチを好むと思いますが、各アプローチの長所と短所を知りたかったのです。たとえばクラスアプローチを使用した場合、オーバーヘッドは発生しますか?
解決
オーバーヘッドは問題ではありませんが、名前空間にはいくつかの利点があります
- 別のヘッダーの名前空間を再開し、コンパイルを低く抑えながら物事をより論理的にグループ化できます
名前空間エイリアシングを有利に使用できます(デバッグ/リリース、プラットフォーム固有のヘルパー、....)
例えば私は次のようなことをしました
namespace LittleEndianHelper { void Function(); } namespace BigEndianHelper { void Function(); } #if powerpc namespace Helper = BigEndianHelper; #elif intel namespace Helper = LittleEndianHelper; #endif
他のヒント
使用できるケース class
(または struct
) 以上 namespace
型が必要な場合です。たとえば、次のようになります。
struct C {
static int f() { return 33; }
};
namespace N {
int f() { return 9; }
}
template<typename T>
int foo() {
return T::f();
}
int main() {
int ret = foo<C>();
//ret += foo<N>(); // compile error: N is a namespace
return ret;
}
Pieter の優れた応答に付け加えると、名前空間のもう 1 つの利点は、名前空間に置いたもの、特に構造体を前方宣言できることです。
//Header a.h
// Lots of big header files, spreading throughout your code
class foo
{
struct bar {/* ... */);
};
//header b.h
#include a.h // Required, no way around it, pulls in big headers
class b
{
//...
DoSomething(foo::bar);
};
そして名前空間についても...
//Header a.h
// Big header files
namespace foo
{
struct bar {/* ... */);
}
//header b.h
// Avoid include, instead forward declare
// (can put forward declares in a _fwd.h file)
namespace foo
{
struct bar;
}
class b
{
//...
// note that foo:bar must be passed by reference or pointer
void DoSomething(const foo::bar & o);
};
最終的に数百のソース ファイルにまたがるプロジェクトになった場合、前方宣言により、小さなヘッダー変更後のコンパイル時間に大きな違いが生じます。
パーシーバルから編集
答えはあまりにも優れていたので、列挙型エラーが原因で停止することはできませんでした (コメントを参照)。enum (現在の C++ ではなく、C++0x でのみ前方宣言できる) を構造体に置き換えました。
名前空間を使用する主な利点は、名前空間を再度開いて、後でさらに内容を追加できることですが、クラスではそれができません。これにより、このアプローチは疎結合ヘルパーに適しています (たとえば、すべての STL が ::std にあるのと同じように、ライブラリ全体に対して Helpers 名前空間を持つことができます)。
クラスの主な利点は、それを使用してクラス内にクラスをネストできることですが、クラス内に名前空間をネストすることはできません。これにより、このアプローチは密結合ヘルパーにとってより適切になります。
名前空間ではなくクラスにそれらを配置しても、余分なオーバーヘッドは発生しません。
名前空間には、Koenig ルックアップの追加の利点があります。ヘルパー クラスを使用すると、コードがより冗長になる可能性があります。通常、呼び出しにヘルパー クラス名を含める必要があります。
名前空間のもう 1 つの利点は、後で読みやすくなることです。クラスでは、特定のクラスがオブジェクトの作成に使用されないことを後で思い出させるために、「ヘルパー」などの単語を含める必要があります。
実際には、どちらにもオーバーヘッドはありません。コンパイル後は、使用される名前マングリングのみが異なります。
私の回答の一部をコピー/トリミング/再加工しました C++ で名前空間を適切に使用するにはどうすればよいでしょうか?.
「使って」を使う
「using」を使用すると、ヘルパー関数の「接頭辞」の繰り返しを避けることができます。例えば:
struct AAA
{
void makeSomething() ;
} ;
namespace BBB
{
void makeSomethingElse() ;
}
void willCompile()
{
AAA::makeSomething() ;
BBB::makeSomethingElse() ;
}
void willCompileAgain()
{
using BBB ;
makeSomethingElse() ; // This will call BBB::makeSomethingElse()
}
void WONT_COMPILE()
{
using AAA ; // ERROR : Won't compile
makeSomething() ; // ERROR : Won't compile
}
名前空間の構成
名前空間は単なるパッケージではありません。別の例は、Bjarne Stroustrup の「The C++ Programming Language」にあります。
「特別版」では、 8.2.8 名前空間の構成, では、AAA と BBB という 2 つの名前空間を CCC という名前の別の名前空間にマージする方法について説明しています。したがって、CCC は AAA と BBB の両方のエイリアスになります。
namespace AAA
{
void doSomething() ;
}
namespace BBB
{
void doSomethingElse() ;
}
namespace CCC
{
using namespace AAA ;
using namespace BBB ;
}
void doSomethingAgain()
{
CCC::doSomething() ;
CCC::doSomethingElse() ;
}
異なる名前空間から選択したシンボルをインポートして、独自のカスタム名前空間インターフェイスを構築することもできます。これの実用的な使い方はまだ見つかっていませんが、理論的には素晴らしいものです。
私はヘルパー関数を作成するときに匿名名前空間を使用する傾向があります。これらは (一般に) それらに関係するモジュールによってのみ表示されるため、依存関係を制御する良い方法です。