Modern C++編【言語解説】 第4章 名前空間

先頭へ戻る

このエントリーをはてなブックマークに追加

この章の概要

この章の概要です。

名前空間

名前空間は、プログラム内で使われる名前(変数名、関数名など)をグループ分けする仕組みです。 この仕組みによって、同一の名前であっても、グループの名前によって両者の区別を付けることができるため、 名前の衝突を避けられます。

例えば、int型の配列の要素をコピーする関数を copy と名付け、生徒の得点データをコピーする関数も copy と名付けてしまうと、 名前が衝突してしまいます。 こういうとき、C言語であれば、前者を int_array_copy、後者を student_copy のようにして、 プリフィックスを付けるようにして区別を付けます。

C++ の名前空間の仕組みも、考え方はほとんど同じです。 名前空間を使う場合、次のように書きます。

namespace int_array {
    void copy(int* dest, const int* src);
}

namespace student {
    void copy(Data* dest, const Data* src);
}

namespaceキーワードに続けて、名前空間の名前を書きます。 更に { } で範囲を指定すると、{ } の内側の定義が名前空間に含まれるようになります。

この状態で、それぞれの関数を呼び出す際には、

int_array::copy(dest, src);
student::copy(dest, src);

というように、「名前空間名::名前」という構文を使います。 ここで、:: は、スコープ解決演算子と呼ばれており、名前空間だけに使われる記号でないことを断っておきます。他の場面での使われ方は、第19章で説明します。

ここまでの例では、関数を名前空間に入れていますが、変数でも構造体や列挙型などでも同様です。

#include <iostream>
#include <cstring>

namespace student {

    enum Gender {
        Man,
        Woman
    };

    struct Data {
        char name[128];
        Gender  gender;
        int  score;
    };

    void copy(Data* dest, const Data* src)
    {
        std::memcpy(dest, src, sizeof(Data));
    }
}

int main()
{
    student::Data data1 = {"Saitou Takashi", student::Man, 80};

    student::Data data2;
    student::copy(&data2, &data1);

    std::cout << data2.name << "\n"
              << data2.gender << "\n"
              << data2.score << std::endl;
}

実行結果:

Saitou Takashi
0
80

このサンプルプログラムでは、生徒を管理する情報のすべてが student名前空間に収められています。 このように、何らかの意味のある集まりで、1つの名前空間を形成するのが普通です。

このコードを観察してみると分かるように、同じ名前空間の中では、名前空間名::名前の形でなくてもアクセスできます。 例えば、student::copy関数の仮引数に使われている Data は、正確に表現すると student::Data ですが、 この関数宣言そのものが student名前空間の中にあるので、「student::」は省略できます。 つまり、同一の名前空間内にいるのなら、わざわざ名前空間名を指定する必要はありません(しても構いません)。


なお、唯一、名前空間に入れることができないのが、プリプロセッサの定義です。 例えば、

namespace student {

    #define SCORE_MAX (100)

}

このような定義のとき、student::SCORE_MAX のような形でアクセスすることはできません。 名前空間内に定数が必要なら、const変数を使います。

namespace student {

    const int SCORE_MAX = 100;

}

これなら、student::SCORE_MAX という形でアクセスできます。

名前空間の分割

同じ名前の名前空間を複数の場所で定義すると、それらは同一の名前空間とみなされます。 これができないと、ソースファイルとヘッダファイルをうまく書けません。

// student.cpp

#include "student.h"

namespace student {
	void copy(Data* dest, const Data* src)
	{
	}
}
// student.h

namespace student {

    enum Gender {
        Man,
        Woman,
    };

    struct Data {
        char name[128];
        Gender  gender;
        int  score;
    };

    void copy(Data* dest, const Data* src);
}

student.cpp と student.h のそれぞれに、student名前空間が定義されていますが、 両者は同じ名前なので、同じ名前空間のことを意味するものとみなされます。

これまでの章で、std::cout のような記述が登場していますが、この std も名前空間です。 C++標準ライブラリはすべて std名前空間に含まれていますが、幾つものヘッダが提供されていることから分かるように、 それぞれの定義が、別個のファイルに記述されています。 これができるのも、同一の名前空間を別個のファイルに記述できるからです。

名前空間のネスト

名前空間はネスト(入れ子)することができます。

namespace A {
    namespace B {
        void func();
        void func2();
    }
}

この場合、func() を呼び出すには、名前空間の外側からは、A::B::func() という形になります。
冒頭で見たように、同一の名前空間内からは、名前空間名を指定しなくても良いので、 func2() の中から func() を呼び出す際には、単に func() と書けます。

各階層に関数があった場合はどうでしょう。 例えば、次のような場合を考えます。

namespace A {
    namespace B {
        void func();
        void func2();
    }
    void func3();
}

この場合、func3 の完全な名前は「A::func3」です。 つまり、A という名前空間内にあるので、ここから func() を呼び出す際には、 「A::」の部分を省略して、B::func() と書くことができます。

逆に、func() から func3() を呼び出す場合、func() は A::B の中にあるので、 このまま「func3()」と書くと、「A::B::func3」のことになってしまい、 そのような関数は存在しないのでエラーになります。
この場合は、「A::func3」と書かなくてはなりません。


名前空間のネストは、自分用のライブラリを作る場合に活用できます。 例えば、mylib という名前空間を定義し、すべての関数や列挙型などの定義をその中に収めます。 更に、mylib の中を分割し、数学的な処理を math名前空間に入れたり、汎用的なユーティリティ関数を util名前空間に収めたりするのです。

// math.h
namespace mylib {
	namespace math {
	}
}
// util.h
namespace mylib {
	namespace util {
	}
}

こうすれば、他のライブラリ作者が提供する math名前空間や util名前空間と被る可能性を減らせます。

C++17 (ネストした名前空間の新記法)

C++17 で、ネストした名前空間を定義しやすくする新しい記法が追加されました。

#include <iostream>

namespace A::B {
    void func()
    {
        std::cout << "OK" << std::endl;
    }
}

int main()
{
	A::B::func();
}

実行結果:

OK

名前空間を定義する際に、「A::B」のように「::」で名前を連結して記述します。 この場合、

namespace A {
    namespace B {
    }
}

このように書いた場合と同じ意味になります。

この機能は、VisualC++ 2017 ではコンパイラオプションの設定で使えるようになります。 プロジェクトのプロパティから、[構成プロパティ]>[C/C++]>[言語]>[C++ 言語標準] を [ISO C++ 標準の最終草案 (/std:c++latest)] に変更して下さい。

無名名前空間

名前のない名前空間というものがあります。 これを無名名前空間と呼び、次のように記述します。

namespace {
}

namespaceキーワードの直後を省略するだけで、あとは通常の名前空間と同じです。

無名名前空間は、それが書かれたソースファイルからのみ、アクセスできる場所を提供します。 これは、C言語で、staticキーワードを使って、ファイルスコープにすること(C言語編第23章参照)と同じ意味合いを持っています。C++ でも、static を使って同じことはできますが、無名名前空間は、構造体や列挙体の定義などにも適用できるため、適用力は高いです。

なお、無名名前空間の内部にある変数や関数などを参照する際には、スコープ解決演算子を必要としません

namespace {
	const int FILE_PATH_MAX = 260;
}
//static const int FILE_PATH_MAX = 260;  // 静的グローバル変数より、無名名前空間の方が推奨される


int main()
{
    char path[FILE_PATH_MAX];  // ::演算子は必要ない
}

グローバル名前空間

グローバル名前空間は、namespace のブロックに囲まれていない場所のことです。

グローバル名前空間には、当然、スコープ解決演算子を使わずにアクセスできます。 また、スコープ解決演算子を使い、「::」の手前を記述しないようにすることでも、 グローバル名前空間をアクセスできます。

#include <iostream>

void func()
{
    std::cout << "OK" << std::endl;
}

int main()
{
    func();
    ::func();
}

実行結果:

OK
OK

なお、プログラムの開始位置である main関数については、必ずグローバル名前空間に置かなければなりません。

usingディレクティブ(using指令)

名前空間を使うことによって、名前の衝突を避けることができるものの、 毎回「xxxx::」のような修飾を加えるのは面倒ですし、コードが読みづらくなることもあります。
そこで、特定の名前空間について、このような修飾を省略できるようにする機能があります。 これを usingディレクティブ(using指令)と呼びます。

第3章で登場したプログラムで試してみます。 元々は、次のようなコードでした。

#include <iostream>
#include <cstring>

int main()
{
	const char* str = "abcde";
	std::cout << str << std::endl;

	std::size_t len = std::strlen(str);
	std::cout << len << std::endl;
}

「std::」が頻繁に登場しています。 ここに、usingディレクティブを加えると、次のように書けます。

#include <iostream>
#include <cstring>

using namespace std;

int main()
{
    const char* str = "abcde";
    cout << str << endl;

    size_t len = strlen(str);
    cout << len << endl;
}

usingディレクティブは、「using namespace 名前空間名;」という構文になっています。 この一文以降、指定した名前空間は、明示的に指定しなくてもアクセスできるようになります。 効力が消えるのは、この一文を記述したブロックの末尾に達したところです。 そのため、この機能を使うのであれば、極力狭い範囲でのみ有効になるように、関数内などの限定された場所で使うことが望ましいです。

usingディレクティブは、便利であると同時に、混乱を招く可能性もあることを意識しておく必要があります。 例えば、

namespace util {
    int func();
}

int func();

int main()
{
    using namespace util;

    func();
}

このようなプログラムで、main関数内で呼び出している func() は、util::func() と ::func() のどちらになるでしょうか。 このプログラムでは、曖昧であるとみなされてコンパイルエラーになります。
このプログラムから曖昧さを無くすには、func() の呼び出し部分を、

util::func();

か、

::func();

のように明示的に書かなくてはなりません。 これは結局、using namespace util; の意味を失わせます。
明示的に名前空間名を指定することによって、ソースコードの可読性が大きく損なわれるのでなければ、 基本的には usingディレクティブを使わずに、明示的に書いた方が良いです。

また、このような混乱を回避するためにも、usingディレクティブをヘッダファイル側に記述しないようにして下さい。 ヘッダファイルは、様々な場所からインクルードされるため、usingディレクティブの効力を、 他のソースファイルやヘッダファイルにまで撒き散らしてしまいます。 結果的に、何が省略されているのか把握しづらくなり、混乱の元になります。
従って、ヘッダファイル内では、面倒でも、常にスコープ解決演算子を使って明示的に名前空間名を指定すべきです。

using宣言

using宣言は、ある名前を、現在のスコープ内に取り込む機能です。 再び、先ほどのプログラムを例に挙げます。

#include <iostream>
#include <cstring>

int main()
{
    const char* str = "abcde";
    std::cout << str << std::endl;

    std::size_t len = std::strlen(str);
    std::cout << len << std::endl;
}

ここで、std::cout と std::endl は複数回登場しており、これらだけでも簡潔に記述したいとします。 こういう場合に、using宣言が使えます。

#include <iostream>
#include <cstring>

int main()
{
    using std::cout;
    using std::endl;

    const char* str = "abcde";
    cout << str << endl;

    std::size_t len = std::strlen(str);
    cout << len << endl;
}

using宣言は、「using 名前空間名::名前;」という構文になっています。 using宣言の意味合いとしては、指定した名前を現在のスコープに持ち込むということになります。 効力が消えるのは、この一文を記述したブロックの末尾に達したところです。
例えば、

#include <iostream>

namespace util {
    int value = 50;
}

int main()
{
    using util::value;  // util::value が value という名前でスコープ内に追加される

    int value = 10;

    std::cout << value << std::endl;
}

この場合、main() で宣言されている value の他に、util::value が value という名前で main() のスコープ内に追加されることになります。 そのため、main() の value と、util::value とが重複宣言とみなされてコンパイルエラーになります。

一方で、usingディレクティブの場合、

#include <iostream>

namespace util {
	int value = 50;
}

int main()
{
	using namespace util;  // util:: を省略できるようになる

	int value = 10;

	std::cout << value << std::endl;
}

これは、コンパイルエラーにはならず、10 が出力されます。 usingディレクティブは、名前をスコープに追加するという効果ではなく、 単に「util::」と書くべき場面で「util::」を省略できるようにするだけです。
value という名前だけでのアクセスは、C言語のルールと同様に、よりローカルに近いところから名前を探し、 main() の中で宣言されているローカル変数の value を認識します。 したがって、何も曖昧さはないので、コンパイルエラーとはならないのです。


usingディレクティブよりは、using宣言を使う方が、影響範囲が狭いので望ましいと言えます。 もちろん、using宣言も、usingディレクティブ同様、ヘッダファイル内での使用は避けた方が良いです。

エイリアス

名前空間には別名(エイリアス)を付けることができます。 この機能によって、長い名前の名前空間を短くしたり、ネストした名前空間の完全名を短くしたりできます。

#include <iostream>

namespace mylib {
    namespace util {
        void func()
        {
            std::cout << "OK" << std::endl;
        }
    }
}

int main()
{
    namespace ut = mylib::util;

    mylib::util::func();
    ut::func();
}

実行結果:

OK
OK

名前空間のエイリアスは、「namespace 別名 = 元の名前;」という構文で作ることができます。 上のサンプルプログラムの場合、mylib::util名前空間に、ut という別名を付けています。

この機能は、例えば、あるライブラリを提供する側が、最初に公開したバージョン1の機能に対して、 新たにバージョン2を付け加えたいという場合に利用できます。

// ライブラリ側のコード
namespace XXLib {
    namespace v1 {
        // バージョン1の機能
    }
}

// ライブラリ使用者側のコード
namespace lib = XXLib::v1;  // XXLib のバージョン1を使う

lib::func();  // バージョン1の func() を呼び出す

この例では、XXLib という他者が提供したライブラリのうち、バージョン1の部分を使おうとしています。 毎回、XXLib::v1::func() のように書くのではなく、バージョン番号を表す名前空間名に lib という別名を付けて、 lib::func() と書くようにしています。
この後、XXLib の提供者が、バージョン2を公開します。

// ライブラリ側のコード
namespace XXLib {
    namespace v1 {
        // バージョン1の機能
    }
    namespace v2 {
        // バージョン2の機能
    }
}

// ライブラリ使用者側のコード
namespace lib = XXLib::v2;  // XXLib のバージョン2を使う

lib::func();  // バージョン2の func() を呼び出す

ライブラリ側は、v1 の機能を残したままで、v2 の機能を追加します。 使用者側は、エイリアスを定義している箇所を修正し、lib という名前が XXLib::v2 の方を意味するように書き換えます。 これで、v2 への移行が(ライブラリ側の重大な仕様変更がなければ)完了するという訳です。 バージョン1の機能に戻したければ、エイリアスを XXLib::v1 に修正すれば良いです。


この機能もまた、便利であると同時に混乱を招く可能性をはらんでいます。 可能であれば使用を避け、ヘッダファイルでの使用は禁止とすべきでしょう。

インライン名前空間

インライン名前空間という機能は、 名前空間名による修飾を省略しても、その名前空間内へアクセスできるというものです。

#include <iostream>

namespace mylib {
    inline namespace util {
        void func()
        {
            std::cout << "OK" << std::endl;
        }
    }
}

int main()
{
    mylib::util::func();
    mylib::func();
}

実行結果:

OK
OK

名前空間を作る際に、inlineキーワードを付けて、「inline namespace 名前空間名」とすれば、 インライン名前空間になります。
このサンプルプログラムでは、util名前空間がインライン名前空間になっています。 ですから、「mylib::func();」のように呼び出すことが可能になります。


インライン名前空間は、「エイリアス」の項で取り上げた、 ライブラリの例の別解として使えます。

// ライブラリ側のコード
namespace XXLib {
    inline namespace v1 {
        // バージョン1の機能
    }
}

// ライブラリ使用者側のコード
XXLib::func();  // バージョン1の func() を呼び出す

このように、バージョン固有の機能をインライン名前空間で提供しておけば、ライブラリ使用者側は、 「v1::」の部分を記述する必要がありません。

この後、ライブラリ提供者が、バージョン2の機能を v2名前空間に入れて追加提供するとします。 ここで、v1 の方を非インライン名前空間、v2 の方をインライン名前空間にすれば、 ライブラリ使用者側は一切の変更なく、バージョン2の機能を使用できます。

// ライブラリ側のコード
namespace XXLib {
    namespace v1 {
        // バージョン1の機能
    }
    inline namespace v2 {
        // バージョン2の機能
    }
}

// ライブラリ使用者側のコード(変更なし)
XXLib::func();  // バージョン2の func() を呼び出す


練習問題

問題① 次のプログラムの出力結果を答えて下さい。

#include <iostream>

void func()
{
    std::cout << "::func()" << std::endl;
}

namespace util {
    void func()
    {
        std::cout << "util::func()" << std::endl;
    }
}

int main()
{
    using util::func;

    func();
}

問題② 問題①のプログラムで、main関数の内側にある using宣言を、

using namespace util;

に変更した場合、実行結果はどうなりますか?


解答ページはこちら

参考リンク

更新履歴

'2018/1/15 無名名前空間の説明を修正。
C++11 では、スコープを指定するための static を削除予定としていないので、無名名前空間を推奨させるような記述を削除した。

'2018/1/5 Xcode 8.3.3 を clang 5.0.0 に置き換え。

'2017/7/30 clang 3.7 (Xcode 7.3) を、Xcode 8.3.3 に置き換え。

'2017/7/8 新規作成。



前の章へ

次の章へ

Modern C++編のトップページへ

Programming Place Plus のトップページへ