C++入門/名前空間

柴田祐樹,東京都立大学,情報科学科
戻る

C++ にはスコープという概念がある事は以前説明した.スコープは階層的に定義され,上位階層の変数は下位のスコープから参照可能であるが,逆は不可能である.例えば以下はグローバルスコープに a, main 関数の局所スコープに b, さらに無名のスコープに c が定義されているが,6 行目のように上位のスコープに存在する a は参照できたとしても,8 行目のような下位のスコープに存在していたものを上位スコープから参照することはできない.つまり,このコードはコンパイルすることができない.

int a=0;
int main(){
    int b=1;
    {
        int c=2;
        a = 5;
    }
    c = 3; // NG
    return 0;
}

スコープは上記のように変数の参照可能な範囲を制限することでプログラムの可読性を向上させることができるが,名前空間はもう少し一般化させたもので,これを使えばスコープに名前を付けて,変数の参照範囲を特定のスコープに展開させることができる.また,変数をまとめて扱うことができる.以下に例を示す.ここでは1行目で名前空間 C が定義されている.この名前空間には識別子 c, a が属する.7 行目では名前空間 C の利用を宣言している.この宣言がなされたスコープでは,その名前空間内の識別子を使うことが可能となる.つまり,8, 9行目に記載されている通り,変数 c, a が利用可能になる.しかし名前空間の利用は宣言されたスコープ内に限られるため,11行目で記載されている様な a の使い方をすることはできない.13行目では再度名前空間 C の利用を宣言しているため,このスコープの範囲では c, a を再び使うことができる.このコードはコンパイルすることができない.

namespace C{
    int c;
    int a;
}
int main(){
    {
        using namespace C;
        c = 1;
        a = 2;
    }
    a = 1; // NG
    {
        using namespace C;
        c = 2;
        a = 5;
    }
    c = 3; // NG

    return 0;
}
 

名前空間は変数名の衝突回避に使うことが可能である.例えば,以下の例では同一の識別子 c が名前空間 C, B にそれぞれ宣言されているが,プログラム上はこれらは別の名前空間に属するため,別の変数として扱われる.発想としては姓が異なり名前が同じ人が姓名により区別されることと同じである.空間と言うからには集合の演算が使えるのだろうかと思うが残念ながらそこまで洗練されたものではない.この例の利用では,29行目で C, 33行目で B の利用をそれぞれ宣言している.そのため,10行目で影響を受ける c は 2 行目で宣言されたものであり,14 行目で影響を受けるのは 5 行目のものである.ここでは更に,名前空間の利用を宣言せず,それぞれの変数を参照する方法を 16, 17 行目に示してある.名前空間を接頭辞として記載し :: を付けて変数名を参照すれば,変数の参照先を区別することができる.実際に,ここで a に代入されるのは 1 であり,b に代入されるのは 2 となることが,実行すれば確認できるだろう.

namespace C{
    int c;
}
namespace B{
    int c;
}
int main(){
    {
        using namespace C;
        c = 1;
    }
    {
        using namespace B;
        c = 2;
    }
    int a = C::c;
    int b = B::c;
    return 0;
}

名前空間中に宣言されたものは,互いを接頭辞無しに参照可能である.例を以下に示す.名前空間 C, B にそれぞれ変数 c とそれに対し 1 を加算する関数 f がどちらの名前空間中にも定義されている.4行目,10行目には変数 c が接頭辞無しで記載されているが,ここで参照されるのは 4 行目では 2 行目の c, 10行目では 8 行目の c となる.実際にこの関数を16行目では名前空間 C を指定して,20, 21 行目では名前空間 B を指定してそれぞれ呼び出してみている.それぞれ,C::c, B::c が変更されるはずであるが,B の方は二度 f を呼び出しているため,B::c の値は 2 増加するはずである.実際に,23, 24 行目のように値を取得すればこれを確かめることができるはずである.

namespace C{
    int c = 1;
    void f(){
        c = c + 1;
    }
}
namespace B{
    int c = 1;
    void f(){
        c = c + 1;
    }
}
int main(){
    {
        using namespace C;
        f();
    }
    {
        using namespace B;
        f();
        f();
    }
    int a = C::c;
    int b = B::c;
    return 0;
}

C++ には標準雛形ライブラリ(Standard Template Library: STL) が用意されている.それらは全て名前空間 std 内で定義されている.画面出力のための cout などもこの std の中に定義されているため,画面出力を行うだけの初学者用の以下の様なコードでも最低限 std の利用を宣言する必要が有る.いきなりこのページの説明を読んだところで理解は難しいだろうが,ここに到達するまでに理解せずとも2行目の名前空間 std の利用宣言を記載してプログラムを書いて慣れる努力をされていた者ならば,以下の意味するところがよく分かるだろう.

#include <iostream>
using namespace std;
int main(){
    cout << "Hello\n";
    return 0;
}

多人数でプログラムを共同開発する場合は最低限名前空間で自分のコードを囲っておくと良い.以下に例を示す.私の名前は祐樹(Hiroki)なのでその名前空間を定義した.もちろんこれくらいの名前だと競合する可能性は高いので,より良い使い方を知る必要は有るが,最初のうちは以下の程度で十分であり,また変数名のみならず名前空間の競合を回避する術まで必要となるようならそれを自分で調査できる程度に成長しているはずであるから,今は以下の使い方ができれば必要十分である.

#include <iostream>
namespace Hiroki{
    int a;
    int b;
    void print(){
       using namespace std;
        a = a + 1;
        cout << a << "\n";
    };
}
int main(){
    Hiroki::print();
    return 0;
}

名前空間中の要素のうち,特定の物だけを使いたい場合には以下のように書ける.例えば以下の7行目から11行目のスコープ(ブロック)では,std::cout, mySpace::b を接頭辞なしで使いたいとする.このとき,それぞれ8, 9行目に using + 名前空間名つき変数名,のように記載することでこのための省略名利用を宣言することができる.実用では必要なものだけこのように利用宣言をすることが望ましい.しかしこの方法では,毎回たくさんの using を書かなければならないという問題が生じる.それを解決するためには,これら using をまとめた名前空間を別に用意すれば良い.その例が,1から5行目の名前空間mySet2の宣言と14行目における利用宣言である.参考にされたい.

namespace mySet2{ // using をまとめる方法.
    using std::cout;
    using mySpace::sum;
    using mySpace::b;
}
int main(){
    {
        using std::cout; // cout だけ使いたい場合
        using mySpace::b; // b だけ使いたい場合.
        cout << b << std::endl; // std::endl は利用が宣言されていないので接頭辞が必要
    }

    {
        using namespace mySet2;
        sum();
        cout << b << "\n";
    }
    return 0;
}

最後に,Class と名前空間との関係性を述べる.同じ変数名の競合を避けることは,Class や struct でも可能であるが,Class はオブジェクトを生成するためのもので,名前空間をスコープに展開するためのものではない.似たような使い方ができるが,互いに補えない機能が存在する.さらに例を上げれば,クラスはオブジェクトを生成することで,同じクラス内で定義されるメンバ変数であってもオブジェクト毎に異なる状態を取ることが可能であるが,名前空間中に定義された変数は一意であり,複製されることはない.有る範囲で囲った領域の機能を複製し,同じ構造で複数の状態を持ちたいなら Classを使い,識別子の衝突を避けたいだけなら名前空間を用いると覚えておくと良い.