namespaceに関する備忘(その1)

namespaceで名前空間を指定すると、同じ関数名でも別の名前空間であればコンフリクト(名前の衝突)しないので、プログラム作成の上で重宝する。
例えば、テンポラリーな処理を行う上でローカルな関数を定義することがあるが、関数名を定義しているのを忘れて、他の場所で同じ名前の関数を作成すると、ほぼ100%理解のできないバグとなって苦しめられる。

// file1.cpp
// 適当なローカルの作業関数
int calc( int x )
{
  return x * 2;
}

// 呼び出し元
void par()
{
  int x = 2;
  int y = calc(  x ); // file1.cpp、file2.cppで実装されているcalcのどちらかが呼ばれる
}
// file2.cpp
// 適当なローカルの作業関数2
int calc( int x )
{
  return 0;
}

// 呼び出し元
void par2()
{
  int x = 2;
  int y = calc(  x ); // file1.cpp、file2.cppで実装されているcalcのどちらかが呼ばれる
}

うかつに上のようなコーディングをしても、コンパイル及びリンクができ、大概の場合名前の衝突に関して警告もエラーも出ない。
そして、どちらかで関数が期待とは異なる値を返すことで理解のできない状況を作り出す。

このような事態の予防のために名前空間を使うことができる。

// file1.cpp
// 適当なローカルの作業関数
namespace file1 {
int calc( int x )
{
  return x * 2;
}
} // end of namespace file1

// 呼び出し元
void par()
{
  int x = 2;
  int y = file1::calc(  x ); // file1.cppで実装されているcalcが呼ばれる
}
// file2.cpp
// 適当なローカルの作業関数2
namespace file2 {
int calc( int x )
{
  return 0;
}
} // end of namespace file2

// 呼び出し元
void par2()
{
  int x = 2;
  int y = file2::calc(  x ); // file2.cppで実装されているcalcが呼ばれる
}

上のように名前空間で縛れば、コーディングした期待通りに動作する。
名前空間ではなくユニークな関数名、例えばローカルな関数はファイル名+関数名にするというルールでも同じ結果は得られるが、名前空間でうれしいのは下のように、名前空間を省略できることがある。

// file1.cpp
// 適当なローカルの作業関数
namespace file1 {
int calc( int x )
{
  return x * 2;
}
} // end of namespace file1

// 呼び出し元
using namespace file1; // 名前空間file1を使うことを宣言
void par()
{
  int x = 2;
  int y = calc(  x ); // file1.cppで実装されているcalcが呼ばれる
}

呼び出し元が増えると、タイプ量も大分変わるのでうれしい。
ただし、注意点として名前空間を使っていても、インクルード関係が無いなど別個に定義された全く同じ名前空間、関数名の場合はコンパイラーもリンカーもエラーや警告を出さないので、名前空間の定義には慎重になる必要がある。まあ、通常同一ファイル名の重複はできないのでファイル名を基に名前空間命名することが良いだろう。
クラス内関数からの呼び出しであれば、ローカル関数はクラスに定義した方が良い場合が多いのでそうすべきだろう。
頻繁に仕様が変わる可能性がある場合や、DLLのインターフェースなど関数の仕様を外部には出したくない場合には、こちらの方法をとるのが良いだろう。

2022-12-08 追記

上記でも可能だが、無名名前空間を用いればもっと簡単なことが判った。

// file1.cpp
// 適当なローカルの作業関数
namespace {
int calc( int x )
{
  return x * 2;
}
} // end of unnamed namespace 

// 呼び出し元
void par()
{
  int x = 2;
  int y = calc(  x ); // file1.cppで実装されているcalcが呼ばれる
}
// file2.cpp
// 適当なローカルの作業関数2
namespace {
int calc( int x )
{
  return 0;
}
} // end of unnamed namespace 

// 呼び出し元
void par2()
{
  int x = 2;
  int y = calc(  x ); // file2.cppで実装されているcalcが呼ばれる
}

無名名前空間は、そのファイルの中でのみ通用することがポイントだ。
かつ、using namespace ~を使わなくとも無名名前空間外で関数名を用いることができる。