比較的最近登場したC++の機能の中に、「ネームスペース(名前空間)」というものがあります(シェルエクステンションの「ネームスペース」とは別物です)。この機能はさっそくSTLに使用されているので、存在くらいは知っていることでしょう。
今回は、どういう機能を持ったものなのか、簡単に見ていこうと思います。 |
「ネームスペース」とは |
クラス、関数、変数、構造体などの「名前」には、これまで多くのプログラマーが悩まされてきました。これらの名前には「見ただけでどんなものか判る」「他の名前とかち合わない」「「長すぎずコーディングしやすい」といったことが求められます。プログラマーはこれらを満たす「命名規則」に沿ってコードを書きます。が、それはしばしばそのプログラマー独自のもので、「すべてのプログラマーが満足する」名前というものは存在しませんでした。
C++の「ネームスペース」は、「命名規則」の手助けになるものです。#defineやtypedefといったものを使用せず、言語として「判りやすく汎用性がある名前」を作成できるようサポートする機能、それがネームスペースです。 たとえば、あなたのライブラリでGet()という関数を作成したいと考えます。でも、この名前はあまりにもストレートすぎます。これまでは、こういう場合__Get()やMyGet()のような名前を付けてきました。ですが、ネームスペースでは違います。 ネームスペースが使える場合にはMy::Get()という名前にすることができます。ネームスペースの便利なところは、必要ならGet()とシンプルに関数を呼び出すこともできるという点です。つまり「名前の短縮がサポートされている」ということです。 これらは変数、クラス、関数など、さまざまなものに適用でき、また、様々なものにまとめて名前を付けることができます。 では、その実際の方法を見ていきましょう。 |
「名前」で囲む |
クラスや関数を名前で囲むときには、次のようにnamespaceというキーワードを使用します。
|
////////////////////////////////// // ヘッダーファイル内。 namespace NameA //"NameA"という名前で囲みます。 { class CTest { public: void Test1(); void Test2() { TRACE0( "NameA::CTest::Test2()\n" ); return; } }; void Test(); } //namespace NameA //ここまで囲みました。 ////////////////////////////////// // ソースファイル内。 void NameA::CTest::Test1() { TRACE0( "NameA::CTest::Test1()\n" ); return; } namespace NameA //"NameA"という名前で囲みます。 { void Test() { TRACE0( "NameA::Test()\n" ); return; } } //namespace NameA
まず「ヘッダーファイル」の方を見てください。「NameA」という「名前」で囲んであります。この囲んだ中が「NameA というネームスペース」ということになります。
ネームスペース内のオブジェクトは「ネームスペースの名前::オブジェクト名」という名称になります。上の例だと、「NameA::CTest」とか「NameA::Test()」という名前になります。 実装時にはそれを踏まえる必要があります。CTest::Test1()はNameA::CTest::Test1()という名前で書く必要があります。また、その下のNameA::Test()の例のように、実装部も同じネームスペースで囲んでもOKです。これと同じ原理で、ヘッダーファイルでインライン関数として実装しても構いません。 この例で分かるように、「同じ名前のネームスペースは同じネームスペースとして認識される」ことになります。たとえば、上のヘッダーファイルの例は次のコードと同じ意味になります。 |
////////////////////////////////// // ヘッダーファイル内。 namespace NameA //"NameA"という名前で囲みます。 { class CTest { public: void Test1(); void Test2() { TRACE0( "NameA::CTest::Test2()\n" ); return; } }; } //namespace NameA //ここまで囲みました。 namespace NameA //"NameA"という名前で囲みます。 { void Test(); } //namespace NameA //ここまで囲みました。
ネームスペースをいくつかに分けても、同じ名前なら同じネームスペースです。
また、ネームスペースはグローバルなオブジェクトしか囲めません。たとえば、関数内やクラス内ではネームスペースを作ることはできません。ですが、ネームスペースをさらにネームスペースで囲むこと、つまり「ネームスペースのネスト」はできます。 |
////////////////////////////////// // ヘッダーファイル内。 namespace NameA //"NameA"という名前で囲みます。 { namespace NameB //"NameB"という名前で囲みます。 { class CTestZ //NameA::NameB::CTestZ という名前になりました。 { /* namespace BadName //これはダメ! { //... } */ }; } //namespace NameB //ここまで囲みました。 } //namespace NameA //ここまで囲みました。
ネームスペースのネストを行うと、それだけ名前が増えていきます。「面倒なだけやん」とか思うでしょうが、後で説明するように、これは使い勝手を向上させるでしょう。
あ、ネームスペースのインデントについては、現在考慮中です。今のところ「しない方がいい」と考えています。インデントが深くなること自体、あまり好きでないもので……。 |
「フルネーム」で使う |
では、これらのクラスや関数を使ってみることにします。まずは「省略しない」方法です。
|
// ソースコード内。 // ここはネームスペースで囲まれていません。 void UseNameA() { NameA::CTest cTest; cTest.Test1(); cTest.Test2(); NameA::Test(); }
フルネームで呼び出すときは実装部分の場合と同じように「ネームスペースの名前::オブジェクト名」という形で指定します。これは関数でもクラスでも同じです。これだけです。簡単でしょう。
|
「省略名」で使う |
今度は「ネームスペースの名前」を省略して使ってみることにしましょう。省略するときにはusingというキーワードを使用します。
|
// ソースコード内。 // ここはネームスペースで囲まれていません。 void UseNameA2() { using NameA::CTest; //省略を指定します。 CTest cTest; //すると、省略形で書けます。 cTest.Test1(); cTest.Test2(); // Test(); //指定してないのはダメ。 using namespace NameA; //これで"NameA"を省略できます。 Test(); //今度はOK。 NameA::Test(); //これもちゃんと使えます。 // ::Test(); //これはダメ!! // ここで using と using namespace の効力は切れます。 }
usingを使用すると、特定のオブジェクトだけを省略形で書くことができます。上の例だと、NameA::CTestを省略形で書くことができますが、NameA::Test()は省略形では書けません。
using namespaceなら、特定のネームスペースを省略することができます。これだと、NameAの名前が付いたオブジェクトはすべてNameAを省略することができます。 usingとusing namespaceは、これらを使用したスコープの中でしか効力を持ちません。逆に言えば、グローバル空間で使用すると、どこででも省略できることになります。これをうまく使わないと、ネームスペースの意味がなくなってしまうので気を付けてください。 注意して欲しいのは、using namespaceを使って指定したネームスペースがグローバルになるわけではないということです。 単に「省略できるだけ」というだけで、省略しない名称も使えます。また、名前の付いてない「::」だけの指定はできません。この指定は「どのネームスペースにも囲まれてないオブジェクト」を指定するからです。 (だから、これまでAPIに::を着けてたのは、ちょっとまずかったのかも……) |
同一名のオブジェクトを使用する |
さて、ここでもっとも標準的な「ネームスペースの効用」を利用してみましょう。
前述のNameA::CTestの他に、もうひとつCTestを作りたい、という場合には、これもネームスペースで囲んでしまえばいいわけです。 |
////////////////////////////////// // ヘッダーファイル内。 namespace NameA //"NameA"という名前で囲みます。 { class CTest { public: void Test1(); void Test2() { TRACE0( "NameA::CTest::Test2()\n" ); return; } }; } //namespace NameA //ここまで囲みました。 namespace NameB //"NameB"という名前で囲みます。 { class CTest { public: CTest( int p_i ) //コンストラクタです。 : m_i( p_i ) {} int Get() { return m_i; } private: int m_i; }; } //namespace NameB //ここまで囲みました。
このように、同じ名前のクラスを別々のネームスペースに囲むことで、クラス名について気にする必要がなくなります。
これらのクラスは、次のように使用できます。 |
void UseNameAnB() { // フルネームで使用。 NameA::CTest cTestA; NameB::CTest cTestB( 10 ); cTestA.Test2(); TRACE( "%d\n", cTestB.Get() ); // NameA だけ using namespace します。 using namespace NameA; CTest cTestA2; cTestA2.Test2(); // NameB も using namespace します。 using namespace NameB; //これは通ります。 // CTest cTestB2( 100 ); //これはダメ。あいまいです。 NameB::CTest cTestB2( 100 ); //しょーがないからフルネーム指定。 TRACE( "%d\n", cTestB2.Get() ); }
フルネームでの使用はもちろんできます。これがもっとも確実な方法です。
ネームスペースの省略も行えます。まず、「省略」だけなら、NameAとNameB、同時に行えます。先ほど説明したように、省略形が使えるというだけで、実際の名前は変わっていないからです。 でも、さすがに両方とも省略した状態でCTestと省略形で書くことは許されません。NameA::CTestとNameB::CTest、どちらが使用されるのか分からないからです(コピーコンストラクタの指定も意味がありません)。この場合には、フルネームで書けばOKです。 このように、ネームスペースとクラスや関数を組み合わせることで、「名前がかち合う」という問題を簡単に解決できます。フルネームでも、省略形でも書けるわけで、コーディングも楽になるでしょう。 |
まとめ&次回予告 |
以上で分かるとおり、ネームスペースは柔軟で使いやすい機能です。ネームスペースを使った命名法を行えば、分かりやすく変更しやすいコードを書くことができるでしょう。
次回は、このネームスペースをフルに活用する方法を紹介しようと思います。使うか使わないかは、皆さん次第ですが……。 |
(C)KAB-studio 1999 ALL RIGHTS RESERVED. |