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

トップページModern C++編

Modern C++編は作りかけで、更新が停止しています。代わりに、C++14 をベースにして、その他の方針についても見直しを行った、新C++編を作成しています。
Modern C++編は削除される予定です。

この章の概要 🔗

この章の概要です。


名前空間 🔗

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

たとえば、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);

「::」はスコープ解決演算子と呼ばれています。

この演算子は、名前空間以外の用途でも使われます。第23章で説明します。

ここまでの例では、名前空間に入れているのは関数だけですが、変数の宣言や、構造体型や列挙型などの定義でも同様に可能です。

#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)

}

SCORE_MAXマクロは、student::SCORE_MAX とはなりません。依然として、SCORE_MAX という名前でしか使用できません。

名前空間内に定数が必要なら、const を使います。

namespace student {

    const int SCORE_MAX = 100;

}

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

名前空間の分割 🔗

同じ名前の名前空間が複数の箇所に記述されていても、それらは同一とみなされ、1つの名前空間スコープを形成します。このルールがないと、ソースファイルとヘッダファイルをうまく書けません。

// 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(); }
    }
}

この場合、名前空間の外側から func関数を呼び出すには、「A::B::func()」とします。

冒頭で見たように、同一の名前空間内からは、名前空間名を指定しなくても良いので、func2関数の中から func関数を呼び出す際には、単に「func()」と書けます。

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

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

この場合、func3 の完全な名前は「A::func3」です。つまり、A という名前空間内にあるので、ここから A::B::func関数を呼び出すときには、「A::」の部分を省略して「B::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名前空間と被る可能性を減らせます。

無名名前空間 🔗

名前のない名前空間というものがあります。これを無名名前空間と呼びます。

無名名前空間を作る方法は、以下のように、通常の名前空間から名前の指定を省くだけです。

namespace {
}

無名名前空間は、それがある翻訳単位内からだけアクセスできる場所を提供します。つまり、変数や関数に static 付加して内部結合にすることと同じ意味合いを持っていますC言語編第24章参照)。

C++ でも、static を使うことはできますが、無名名前空間は、構造体や列挙体の定義などにも適用できるため、より適用力が高いといえます。

static指定子は使用箇所によって、静的記憶域期間の指定であったり、内部結合の指定であったりするため、用途が一貫しておらず、やや分かりづらいといえます。無名名前空間は意図が明確です。

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

namespace {
    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ディレクティブ以後の場所では、明示的に記述せずに使えます。効力は、usingディレクティブを記述したブロックの末尾に達したところで消滅します。

usingディレクティブの効力は、記述したブロックの末尾に達したところで消滅します。極力狭い範囲でのみ有効になるように、関数内などの限定された場所で使うことが望ましいとされます。

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; の意味を失わせます。

明示的に名前空間名を指定すると、xxx:: のような記述が大量になってしまい、可読性が大きく損なわれるということでもなければ、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宣言は、指定した名前を現在のスコープに持ち込むということになります。効力が消えるのは、この一文を記述したブロックの末尾に達したところです。

std::cout と std::endl がそれぞれ、using宣言があるスコープに持ち込まれたことによって、「std::」を指定せずに cout と endl を使えるようになりました。

なお、using宣言も、usingディレクティブ同様に、ヘッダファイル内での使用は避けた方が良いです。

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関数の中で using宣言を行っており、util::value を指定しています。こうすると、util::value が、value の名前でスコープに持ち込まれます。そのため、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::」を省略できるようにするだけです。

std::cout に渡している value という名前が何を表しているのかは、C言語と同様のルールによって確定されます。つまり、より近いスコープから名前を探しますから、main関数の中で宣言されているローカル変数の value を認識します。util::value のことは認識されません。したがって何も曖昧さはないので、コンパイルエラーとはならないのです。

エイリアス 🔗

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

#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;

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


解答ページはこちら

参考リンク 🔗


更新履歴 🔗

 名前空間メンバ、名前空間メンバ名、名前空間スコープという用語を補った。

 C++編【言語解説】第3章「名前空間」の章の更新に合わせて、修正を加えた。

 「VisualC++」という表現を「VisualStudio」に統一。

≪さらに古い更新履歴を展開する≫



前の章へ (第3章 C言語の機能の強化)

次の章へ (第5章 クラス)

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

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る