【C++】無名名前空間とは【目的と用途、活用例】

無名名前空間

無名名前空間(unnamed namespace)は名前空間の名称を省略した特殊な名前空間です。無名名前空間のスコープ内で宣言された変数や関数は、内部リンケージを持つという特徴があります。これによって他のファイルから参照できない変数や関数を宣言することが可能になります。

// 無名名前空間
namespace {
  int a; // 内部リンケージ
}

static int b; // 内部リンケージ
/*  */ int c; // 外部リンケージ

変数int c;は外部リンケージを持ったグローバル変数となるため、異なるソースファイルからも参照が可能となります。逆に無名名前空間内で宣言された変数int a;はファイルスコープ内でのみ有効な変数となり、他のファイルから値を参照することができなくなります。

内部リンケージに関わるより具体的な説明については、次節以降で詳細に解説します。

ちなみにC++関連のドキュメントやコンパイラによっては、無名名前空間のことを匿名名前空間(anonymous namespace)と呼ぶこともありますが、無名名前空間(unnamed namespace)のほうが正式な名称です。

目次

無名名前空間の特徴と目的

内部リンケージを持つ

無名名前空間内で宣言された変数や関数等のシンボルは内部リンケージを持ちます。つまり外部のファイルからは、それらのシンボルを参照することができなくなります。これによって、ファイルスコープ内でのみ有効な変数や関数を、実装ファイル側で局所的に宣言することが可能になります。それらはstatic宣言された変数や関数と同等の特性を実現するものとなります。

// 無名名前空間
namespace {
  int a = 1; // 内部リンケージを持つ
}

static int b = 2; // 内部リンケージを持つ
/*  */ int c = 3; // 外部リンケージを持つ

内部リンケージの的確な利用は、シンボル名の重複(多重定義)を回避することにも繋がります。なお、C++の世界では内部リンケージ(内部結合)を実現する手段として、staticの代わりに無名名前空間を利用することが推奨されています。

外部から読み取れない

無名名前空間内で宣言されたシンボルは、見た目上はグローバル空間に宣言されてしまっているように見えるかもしれませんが、実際は名前空間が宣言されたファイル内でのみ有効なシンボルとなり、他のファイルや翻訳単位からは実体が読み取れないという特徴があります。

A.cpp

// 無名名前空間
namespace {
  int a = 1;
}

// グローバル変数
int b = 2;

void test() {
  // int aは内部リンケージを持つためそのままアクセスできる
  printf("%d, %d", a, b); // "1, 2"
}

main.cpp

#include "A.hpp"

int main() {
  // A.cppの`int a`は外部リンケージを持たないため参照できない
  // ERROR: Undefined symbols for architecture x86_64: "_a", referenced from:  _main in main.o
  extern int a;
  
  // グローバル変数は外部リンケージを持つため可能
  extern int b; // b == 2
  printf("%d, %d", a, b);
}

main関数内のコードのように、宣言元とは異なる実装ファイル側で変数aの名前解決が行えなくなります。変数bの場合は外部結合となるため、extern宣言による外部参照が有効となります。

このように、無名名前空間を利用することで、変数int aの実体をA.cpp側の実装ファイル内で隠蔽することができました。これはA.cpp側で従来の記法でstatic int a;と書いた際の挙動と同じものでもあります。無名名前空間はstaticによる記法を置き換える用途として利用することができるのです。

無名名前空間のメリットと活用例

コードの横幅が短くコンパクトになる

staticを使っていた時よりもコードの横幅が狭くコンパクトになります。staticキーワードの文字数よりもインデントの文字数のほうが少なくなるケースであれば有効なメリットとなります。

namespace {
  int value;
}
static int value;

コードが読みやすくなる

いままでは内部リンケージを表現するためにstaticキーワードが頻繁に利用されていましたが、無名名前空間を活用すれば、より明確かつ構造的で分かりやすいコードを記述することが可能になります。

// 構造的な書き方

namespace {
  int a;
  int b;
  
  const int c = 99;
  
  const char* d;
}

extern "C" {
   int x = 1;
   int y = 2;
}

const char* e;
// 従来の書き方

static int a;
static int b;

const int c = 99;

static const char* d;

extern "C" int x = 1;
extern "C" int y = 2;

const char* e;

ひと目で内部リンケージを扱っているブロックであることが理解できます。ブロックで囲われることで、どこからどこまでが内部リンケージの宣言なのかが明確になるためです。

ただし、スコープ内で定義される変数や関数が大量になるような場合は、逆に読みづらくなる場合があるかもしれません。その場合は従来のstaticによる宣言を用いたほうが良い場合もあります。冗長的であっても、staticキーワードが用いられていたほうが、個々のシンボルの属性や目的は明確となります。

無名名前空間でカテゴリー分けができる

変数用と関数用で異なる無名名前空間を使い分けるようにすると、より構造的で整理された綺麗なコードが実現できます。

namespace /* 変数用 */ {
  int a;
  int b;
}

namespace /* 関数用 */ {
  void fn_a() {}
  void fn_b() {}
}

namespace /* 定数用 */ {
  const char c = '\0';
  const int  d = 99;
}

namespace /* 文字列定数用 */ {
  const char* E = "e";
  const char* F = "f";
}

このように無名名前空間には、コードに規則性を与えるための意外な活用方法があります。上記サンプルコード内のそれぞれの集合は、共通の性質を持ったエンティティによってのみ構成されている点が特徴的です。各集合の区別が難しい場合は、上記サンプルコードのように概要コメントを付け加えるようにするとよいでしょう。

無名名前空間を使わないとコードが煩雑になりやすい

ちなみに従来の書き方だと、異なるスタイルの宣言(static、extern、関数宣言等)を同一のスコープ内で共存させなければならなくなるため、コードが読みづらく煩雑になりやすくなります。

この煩雑さを解消するためには、適切なコメントが重要となりますが、無名名前空間によるカテゴリー分けの技法に比べると、あまりスマートな解決策とは言えないかもしれません。

/* ここから先、内部リンケージ */
static int a;
static int b;

const int c = 99;

static const char* d;
const char dd[4];

/* ここから先、外部リンケージ */
const char* e;

構造化された綺麗なコードを書く上で、コメントはある種の最終手段と言うべきものです。コメントがなくても理解できるようなコードを書くことが理想です。

無名名前空間が優れている点は、コードの意図や目的を推測するための手がかりを残せる点です。そしてこれは人間に対してだけではなく、コンパイラやIDE、外部ツールにとっての手がかりにもなる点が重要です。

無名名前空間の問題と対処法

名前衝突の回避

無名名前空間で宣言したシンボルも名前衝突の原因になることがあります。この場合、::修飾子による名前解決が必要になります。

namespace {
  int puts(const char* str) {
    return printf("DEBUG: %s\n", str);
  }
}

int main() {
  puts("a");   // ERROR: Call to 'puts' is ambiguous
  ::puts("b"); // <stdio.h>ヘッダー側の関数が呼ばれる
}
広告