名前空間
名前空間とは
C++では名前空間という概念が導入されています。
名前空間は、名前の衝突(同じ名前を付けることで呼び出し先が曖昧になること)を避けるために有効に働きます。
例えば、「stdio.h」には「remove関数」が存在します。
そのため、プログラマが「remove」という名前の自作関数を作ると、stdio.hにあるremove関数なのか自作関数のremove関数なのか、見分けがつかなくなります。
C言語では同じ名前の関数を作ることはできませんが、C++にはオーバーロードという機能があるため、名前が同じでも引数が違えば定義は可能です。
しかし、標準関数なのか自作関数なのか見分けが付きにくくなるので、可能ならば同じ名前の関数は避けたほうが良いでしょう。
(関数のオーバーロードについては関数の新機能で説明します)
名前空間は、関数などの「名前」が存在する「住所」を定義するようなものです。
同じ「山田さん」でも、「1丁目の山田さん」と「2丁目の山田さん」は別の人であると識別できます。
この「1丁目の」「2丁目の」が名前空間に当たります。
先ほどのremove関数の例で言うと、それぞれを「stdio.hのremove関数」「自作関数のremove関数」と呼ぶことで、別の関数と識別することができます。
文字列出力に使用したstd::cout
は、「std
という名前空間に存在するcout
」という意味となります。
::
はスコープ解決演算子(大域解決演算子)と呼ばれます。
スコープ解決演算子という名前からもわかる通り、名前空間は変数のスコープとよく似ています。
(スコープについては変数のスコープ(C言語)参照)
変数が存在する場所によって、現在の場所から参照できるかできないかが決まります。
名前空間は、参照できない場所からでも参照できるように住所を知らせてあげるようなものです。
usingディレクティブ
cout
オブジェクトをを使用したければstd::cout
と記述するわけですが、毎回これを記述していては少し面倒です。
std::
は短いのでまだ楽ですが、もっと長い名前空間や階層の深い場所だと大変になります。
これを解決するためにはusingディレクティブを使用します。
(directive=指令)
#include <iostream>
using namespace std;
int main()
{
//「std::」を省略できる
cout << "ABCDE" << endl;
getchar();
}
コードの冒頭にusing namespace std;
と記述することで、コード中ではstd::
を省略して直接関数名等を記述することができます。
using namespace std;の弊害
using namespace std;
をコードの冒頭に記述しておくと良い、としている解説が時々ありますが、サンプルコードのような小規模なものならばともかく、実際のコードでは冒頭でこれを宣言することはあまりありません。
std
名前空間に存在する名前群は非常に数が多く、中には普通に変数や関数名に付けてしまいそうな名前も割と多いのです。
例えばstd
にはcount
という、配列内の特定の要素数を数える関数があります。
(<iostream>
内に定義されています)
count
というわかりやすい名前は普通に使ってもおかしくありませんが、コード先頭にusing namespace std;
を記述していると名前がややこしくなります。
#include <iostream>
using namespace std;
int count()
{
//何か処理
return 0;
}
int main()
{
int arr[] = { 0, 1, 0, 2 };
int ret1, ret2;
//自作関数count
ret1 = count();
//std::count
ret2 = count(arr, arr + sizeof(arr), 0);
cout << ret1 << endl;
cout << ret2 << endl;
}
上記コードは関数のオーバーロード機能により、引数の異なる関数の多重定義が行われています。
(詳しくは→関数のオーバーロード)
あえて同じ名前を使用している場合は良いのですが、引数宣言が同じ場合はエラーになりますし、自作関数を呼び出されるのかライブラリ関数が呼び出されるのかが分かりづらくなりバグの原因にもなり得ます。
これではせっかく名前空間で識別できるのに、意味がありません。
これを解決するためには、省略したい名前だけを明示する方法があります。
#include <iostream>
//coutとendlだけ「std::」を省略して書ける
using std::cout;
using std::endl;
int count()
{
//何か処理
return 0;
}
int main()
{
int arr[] = { 0, 1, 0, 2 };
int ret1, ret2;
//自作関数count
ret1 = count();
//std::count
//「std::」は省略できない
ret2 = std::count(arr, arr + sizeof(arr), 0);
cout << ret1 << endl;
cout << ret2 << endl;
}
using std::cout;
とすると、cout
だけはstd::
を省略できるようになります。
これをusing宣言と言います。
明示していないものは省略ができないので、名前空間により別の関数であると識別ができるようになります。
なお、usingディレクティブやusing宣言はコードの冒頭以外にも、関数の中に記述することもできます。
関数内に記述すると、その関数内でだけ名前空間の識別が有効になります。
#include <iostream>
void message(const char *s)
{
//この関数内でだけ「std::」を省略できる
using namespace std;
cout << s << endl;
}
int main()
{
//「std::」は省略できない
std::cout << "ABC" << std::endl;
message("DEF");
}
名前空間の定義
名前空間は自分で定義することもできます。
#include <iostream>
namespace myApp
{
using namespace std;
void printTest()
{
cout << "myApp::test" << endl;
}
namespace nest
{
void printTest()
{
cout << "myApp::nest::test" << endl;
}
}
}
int main()
{
myApp::printTest();
myApp::nest::printTest();
std::cin.get();
}
namespace
という修飾子に続いて、好きな名前空間名を記述します。
その後の波括弧ブロック内{}
が新しい名前空間となります。
名前空間の中にさらに名前空間を定義することもできます。
それぞれ別の名前空間に属しますから、同じ名前の関数を定義することもできます。
他人に公開するライブラリなどを作成する場合は、namespace
を使わないと名前空間がごっちゃになるので定義しておくべきです。
グローバル名前空間
全ての名前空間のブロックの外側、つまり名前が与えられていない空間をグローバル名前空間といいます。
#include <iostream>
//グローバル名前空間
namespace myApp
{
//myApp名前空間
void func() {}
void test()
{
//myApp名前空間のfunc関数
func();
myApp::func();
//グローバル名前空間のfunc関数
::func();
}
}
//グローバル名前空間
void func() {}
int main()
{
//myApp名前空間のfunc関数
myApp::func();
//グローバル名前空間のfunc関数
func();
::func();
}
スコープ解決演算子を使用しない場合、現在の位置から見える範囲で最も内側にある名前に一致します。
これは変数のスコープと同じです。
関数名の前に::
と記述すると、グローバル名前空間を指定することができます。
なお、プログラムの開始関数であるmain
関数はグローバル名前空間に記述する必要があります。
同じ名前空間を定義する
同じ名前の名前空間は複数定義することができます。
namespace myApp
{
int myFunctionA()
{
//省略
}
}
int main()
{
myApp::myFunctionA();
myApp::myFunctionB();
}
namespace myApp
{
int myFunctionB()
{
//省略
}
}
ソースコードの分割などで記述場所が分かれる場合でも同じ名前空間を使用できます。
エイリアス
長い名前の名前空間や、深い入れ子構造の名前空間は、記述が面倒だったりコードが見づらくなったりします。
namespace
はこういった名前空間に別名を与えて記述を簡単にすることができます。
これを名前空間のエイリアスといいます。
namespace myApp
{
namespace nest
{
void func() {}
}
}
//「myApp::nest」の別名として「nst」を使用する
namespace nst = myApp::nest;
int main()
{
myApp::nest::func();
nst::func();
}
エイリアス宣言
C++11以降では、using
キーワードを使用するとtypedef
のように型名に別名を与えることができます。
これはエイリアス宣言といいます。
(単にエイリアスとも言います)
//どちらもint型に別名を与えている
typedef int myInt1;
using myInt2 = int;
C++ではエイリアス宣言では可能だかtypedef
ではできない記述方法もあるので、主にusing
が使用されます。
C++版のC標準関数ヘッダファイル
C++でもstdio.h
をインクルードして入出力関数(printf
関数など)を使用することができます。
そのままC言語風に記述しても良いですが、C言語用のヘッダファイルの関数はすべてグローバル名前空間に展開されてしまいます。
C++では、C言語標準のヘッダーファイルはC++用に作り直されています。
ファイル名は例えばstdio.h
はcstdio
、stdlib.h
はcstdlib
、というように頭に「c」が付き、末尾の「.h」が除去された形式となっています。
これらのヘッダファイルをインクルードすることで、C++でもC標準関数が使用できます。
関数はすべてstd
名前空間で定義されています。
//#include <stdio.h>
#include <cstdio>
int main()
{
std::printf("abc");
std::getchar();
}