ブログ 約5分

C++プロジェクトで名前空間を使う7つの一般的な理由

Share this article
C++プロジェクトで名前空間を使う7つの一般的な理由

名前空間は1995年にC++標準へ導入され、通常は次のように定義されます:

名前空間は新しいスコープを定義します。名前空間は、名前衝突を避ける方法を提供します。

最近のC++コードでは名前空間が広く使用されていますが、古いコードの多くではこの機能が使用されていません。 

多くのC++プロジェクトのソースコードを調べた結果、これらのプロジェクトで名前空間が使われる一般的な理由がいくつか見つかりました。

1. 名前衝突を避ける

この場合、名前空間の使用は主にコンパイラに利益をもたらします。コードの可読性や保守性の観点では、開発者に追加の価値を提供しません。

2. コードをモジュール化する

現代的なC++ライブラリは、コードベースをモジュール化するために名前空間を広く使用しており、「機能ごとの名前空間」アプローチを採用しています。機能ごとの名前空間では、機能セットを反映するように名前空間を使用します。単一の機能に関連するすべての項目(その機能に関連する項目だけ)を1つの名前空間に配置します。これにより、高い凝集性と高いモジュール性を持ち、名前空間間の結合が最小限の名前空間が得られます。密接に連携する項目は、互いに近くに配置されます。

Boostは、機能ごとにグループ化する優れた例です。数千の名前空間が含まれており、それぞれが特定の機能をグループ化するために使用されています。

3. 匿名名前空間

名前のない名前空間により、グローバルな静的変数が不要になります。作成した匿名名前空間は、それが定義されているファイル内でのみアクセスできます。

4. enum問題への回避策

C++の「従来の」enumは、その列挙子を周囲のスコープへエクスポートします。そのため、同じスコープ内の2つの異なるenumが同名の列挙子を定義すると、名前衝突につながる可能性があります。

大規模プロジェクトでは、2つの異なるenumが同じ名前を使用しないという保証はありません。この問題はC++11で enum classによって解決されました。enum classは、enum値を暗黙的にenum名のスコープ内に配置します。

C++11以前は、enumを名前空間内に宣言するのが一般的な回避策でした。たとえば、次のようにenumを宣言する代わりに:

enum status{
  status_ok,
  status_error
};

名前空間内で宣言できます:

namespace status{
       enum status{
         ok,
          error
  };
}

多くのC++プロジェクトがこの技法を使用しています。たとえば、Unreal Engineのソースコードでは、この技法が広く使用されています。

5. 規約による詳細の隠蔽

コードがヘッダーファイルに実装されるテンプレートライブラリでは、特定の型が実装詳細であるため直接使用すべきではないことを、ライブラリ利用者へ示すことが有用です。C#では「internal」キーワードがこの目的を果たしますが、C++には公開型をライブラリ利用者から隠す方法がありません。

Boostライブラリの開発者が先駆けた現代的なC++の一般的なイディオムは、モジュールの実装の一部を構成する(つまり公開APIの一部ではない)ものの、公開する必要があるシンボルを、規約によりdetailという名前の独立したサブ名前空間へ分離することです。

たとえば、Boost.Mathのドキュメントには次のように記載されています:

アプリケーションで使用することを意図していない関数は boost::math::detail

C++11で導入されたインライン名前空間には、さらに2つの用途があります:

インライン名前空間が定義されると、 using ディレクティブが、その外側の名前空間へ暗黙的に挿入されます。外側の名前空間を通じて修飾名を検索するとき、インライン名前空間のメンバーは、たとえその名前が外側の名前空間で宣言されていても、暗黙的な using ディレクティブによって取り込まれ、発見されます。

たとえば、次のコードをUSE_INLINE_Bを定義してコンパイルした場合、生成される実行ファイルの出力は1になります。定義しない場合、出力は2になります。

namespace A {
#if USE_INLINE_B
   inline
#endif
   namespace B {
      int foo(bool) { return 1; }
   }
   int foo(int) { return 2; }
}

int main(void) {
   return A::foo(true);
}

この ドキュメント

6. 明示的なインスタンス化と特殊化でインライン名前空間定義を使用する

インライン名前空間の各メンバーは、その外側の名前空間のメンバーであるかのように、明示的にインスタンス化または特殊化できます。たとえばMという名前空間内の明示的なインスタンス化または特殊化のプライマリテンプレートに対する名前検索では、その外側の名前空間集合にMが含まれるインライン名前空間が考慮されます。例:

namespace L {
   inline namespace M {
       template <typename T> class C; 
   }

   template <typename T> void f(T) { /*...*/ };
}

struct X { /*...*/ };
namespace L {
  template<> class C<X> { /*...*/ };    //template specialization
}

int main()
{
  L::C<X> r;
  f(r);  // fine, L is an associated namespace of C
}

この例では、 

M

 は、その外側の名前空間 

L

のインライン名前空間であり、クラス 

C

 はインライン名前空間 

M

のメンバーです。したがって、 

L

 はクラス 

C

明示的なインスタンス化と特殊化でインライン名前空間定義を使用する場合には、次のルールが適用されます:

  • 明示的なインスタンス化は、テンプレート名が修飾されている場合、プライマリテンプレートの外側の名前空間になければなりません。それ以外の場合は、プライマリテンプレートの最も近い外側の名前空間、または外側の名前空間集合内の名前空間になければなりません。
  • 明示的な特殊化宣言は、まずプライマリテンプレートの最も近い外側の名前空間、または外側の名前空間集合内の名前空間のスコープで宣言する必要があります。その宣言が定義でない場合は、後で任意の外側の名前空間で定義できます。

7. ライブラリのバージョン管理でインライン名前空間定義を使用する

インライン名前空間定義を使用すると、複数の実装を持つライブラリに共通のソースインターフェースを提供でき、ライブラリの利用者は、共通インターフェースに関連付ける実装を選択できます。次の例は、明示的な特殊化を伴うライブラリのバージョン管理でインライン名前空間を使用する方法を示しています。

//foo.h
#ifndef SOME_LIBRARY_FOO_H_
   #define SOME_LIBRARY_FOO_H_
   namespace SomeLibrary
   {
      #ifdef SOME_LIBRARY_USE_VERSION_2_
         inline namespace version_2 { }
      #else    
      	  inline namespace version_1 { }
      #endif
      namespace version_1 {
       	 template <typename T> int foo(T a) {return 1;}
      }     
      namespace version_2 {
       	 template <typename T> int foo(T a) {return 2;}
      }
   }  
#endif 

//myFooCaller.C 
#include <Foo.h>
#include <iostream>

struct MyIntWrapper { int x;}; 

//Specialize SomeLibrary::foo()
//Should specialize the correct version of foo()

namespace SomeLibrary {
	  template <> int foo(MyIntWrapper a) { return a.x;}
}

int main(void) {
	  using namespace SomeLibrary;
	  MyIntWrapper intWrap = { 4 };
	  std::cout << foo(intWrap) + foo(1.0) << std::endl;
}

この例をSOME_LIBRARY_USE_VERSION_2_を定義してコンパイルした場合、生成される実行ファイルの出力は6になります。定義しない場合、出力は5になります。関数呼び出し 

foo(intWrap)

をいずれかのインライン名前空間で修飾する場合は、明示的な特殊化が有効であることを確認する必要があります。

Share this article