C++における名前空間の扱いと,それに関連する比較的最近のヘッダファイルの扱いについて調べてみた.
名前空間って何だ?
ヘッダファイルのインクルード
名前空間って何だ?
C++では,名前空間を扱える. 名前は名前空間にあり,名前空間が異なれば,同一の名前を別のものを指し示すものとして扱うことができる. 名前空間は以下のように指定する.
namespace 名前空間名
{
…名前の宣言…
}
{と}で囲まれた部分で使用する名前は,namespaceに続く名前空間名で示される名前空間に属する名前となる. 指し示す際は,名前空間とともに名前を指定する.
名前空間名::名前
以下に名前空間を扱うサンプルを示す.
#include <iostream.h>
namespace hoge
{
int val = 0;
}
namespace fuga
{
// ある名前空間から,別の名前空間の変数を参照する.
int val = hoge::val + 1;
// 関数の宣言
int getValue() {
return val;
}
}
namespace foo
{
// 階層化された名前空間
namespace var {
int val = 2;
}
}
// using namespace hoge;
using namespace foo;
using namespace foo::var;
using fuga::getValue;
int main()
{
std::cout << "value of hoge::val(" << &hoge::val
<< ") = " << hoge::val << std::endl;
std::cout << "value of fuga::val(" << &fuga::val
<< ") = " << fuga::val << std::endl;
// 変数名のみでなく,関数名も名前空間にある.
std::cout << "return value of fuga::getValue() = "
<< fuga::getValue() << std::endl;
std::cout << "value of foo::var::val(" << &foo::var::val << ") = "
<< foo::var::val << std::endl;
// using namespaceで指定してるので,fooやfoo::varは指定しなくても解決される.
std::cout << "value of var::val(" << &var::val << ") = "
<< var::val << std::endl;
std::cout << "value of val(" << &val << ") = "
<< val << std::endl;
// using指定しているので,getValueはfoo::getValueとして扱われる.
std::cout << "return value of getValue() = "
<< getValue() << std::endl;
return 0;
}
実行結果を以下に示す.
value of hoge::val(0x8049f00) = 0
value of fuga::val(0x8049f04) = 1
return value of fuga::getValue() = 1
value of foo::var::val(0x8049cac) = 2
value of var::val(0x8049cac) = 2
value of val(0x8049cac) = 2
return value of getValue() = 1
サンプルのhoge::valがもっとも簡単な名前空間を使用する例である.
namespace hoge
{
int val = 0;
}
にて名前空間hoge内に変数valを宣言する. あるいはnamespaceを使用せずに単に
int hoge::val;
として指定することもできる. 前者はある名前空間に複数の名前を宣言する際に便利で,後者は少数の場合に便利だろうか. この変数valを指定するには,
std::cout << "value of hoge::val(" << &hoge;::val
<< ") = " << hoge::val << std::endl;
のように,hoge::val(名前空間::変数名)のように,スコープ演算子::を使用して指定する. メイン関数は,デフォルトの名前空間にあり,そこから別の名前空間hoge内の変数を参照しているという構図である. 名前空間fugaでは,別の名前空間hogeの変数を参照しているが,その方法は,メイン関数内(=デフォルトの名前空間内)での方法と同じである.
namespace fuga
{
// ある名前空間から,別の名前空間の変数を参照する.
int val = hoge::val + 1;
// 関数の宣言
int getValue() {
return val;
}
}
ところで,ここでいう名前は,変数名のみでなく,クラス名,関数名も含まれる. 上記,名前空間fuga内では関数getValueを宣言している. これを参照するのも,やはり名前空間::関数名である.
// 変数名のみでなく,関数名も名前空間にある.
std::cout << "return value of fuga::getValue() = "
<< fuga::getValue() << std::endl;
名前空間は,階層構造を持つ(ネストする)ことができる. サンプルでは,名前空間foo内に更に子名前空間としてvar,そしてそのvarにて変数valを宣言している.
namespace foo
{
// 階層化された名前空間
namespace var {
int val = 2;
}
}
階層化された場合,名前空間を親から順に::で区切って指定する. サンプルではメイン関数内でfoo::var::valと指定している.
std::cout << "value of foo::var::val(" << &foo;::var::val << ") = "
<< foo::var::val << std::endl;
ある名前空間内の名前がよく使用されたり,階層の深い名前空間の名前を使用する場合,その都度名前空間を指定するのは面倒であり,ソースの可読性も下る. そこで,あらかじめソースで使用する名前を解決する際に使用する名前空間を指定することができる.
using namespace 名前空間名
サンプルでは,fooとfoo::varに対して,この指定をしている.
using namespace foo;
using namespace foo::var;
こうすることで,指定された名前の解決の際に,その名前の頭に名前空間fooやfoo::varが補完される.
// using namespaceで指定してるので,fooやfoo::varは指定しなくても解決される.
std::cout << "value of var::val(" << &var;::val << ") = "
<< var::val << std::endl;
std::cout << "value of val(" << &val; << ") = "
<< val << std::endl;
ただし,using namespace指定した複数の名前空間に同一の名前があり,衝突する場合,コンパイルエラーとなる. 例えば,サンプルのコメントアウトされたhogeのusing namespace指定を有効にしてみる.
using namespace hoge;
using namespace foo;
using namespace foo::var;
すると,単にvalとして示している変数は,hoge::valとfoo::var::valがあり,衝突するので,コンパイルエラーとなる.
namespace.cpp: function 内の `int main()':
namespace.cpp:47: error: use of `val' is ambiguous
namespace.cpp:22: error: first declared as `int foo::var::val' here
namespace.cpp:5: error: also declared as `int hoge::val' here
namespace.cpp:48: error: use of `val' is ambiguous
namespace.cpp:22: error: first declared as `int foo::var::val' here
namespace.cpp:5: error: also declared as `int hoge::val' here
make: *** [namespace] エラー 1
using namespaceは,名前解決の際の名前空間の自動補完をしてくれる. 解決されない名前に対し,名前空間を適用してくれる. これに対し,using指定は,ある名前に対してのみ名前空間を指定できる(※).
using fuga::getValue;
こうすると,単にgetValueを指定した場合は,fuga::getValueと解釈される.
※ Java
import java.io.*;
と
import java.io.File;
の対比に似ているだろうか.
ヘッダファイルのインクルード
ところで,サンプルの冒頭
#include
を
とすると,コンパイル時に警告がでる.
/usr/include/c++/3.3/backward/iostream.h:31 から
include されたファイル中,
namespace.cpp:1 から:
/usr/include/c++/3.3/backward/backward_warning.h:32:2:
警告: #warning This file includes at least one deprecated
or antiquated header.
Please consider using one of the 32 headers found in
section 17.4.1.2 of the C++ standard.
Examples include substituting the header for the
header for C++ includes, or instead of the
deprecated header .
To disable this warning use -Wno-deprecated.
恥かしながら,C++仕様における明記場所を見付けられないのだが,とにかく,*.hファイルをインクルードするのは,C++の古い仕様で,最新の仕様では*.hがつかない. そして,最新のC++ではヘッダファイルと呼ばず,単にヘッダというらしい. で,C++の標準ライブラリは大抵名前空間std内にて宣言が行われている. そこで,ソースファイルの冒頭にてしばしば
using namespace std;
なる記述がある. 今回のサンプルでは毎回
std::cout
等としてきたが,先の指定により,単にcoutと指定できるようになる. で,何だかいろいろ難しいらしいが,とにかく以下のような見解や方針がメジャーらしい.
using namespaceやusingはヘッダでは禁止.
.cppファイルでは解禁.
名前空間stdってなんであんなにでかいんだ?
どうでもいいコードでは上記の限りではない.