C/C++ - 言語仕様編 第11回 〜名前空間と例外処理〜

名前空間

グローバル名前空間

通常、クラスや構造体、関数などはグローバル名前空間と呼ばれる 領域内で宣言され、定義されているとみなされる。

グローバル名前空間は、いかなる場所からも無条件にアクセス可能であるが、 同一の名前空間内では、同じ名前のクラス、あるいはオブジェクトなどは衝突してしまい、 識別することが出来なくなる。

名前空間とスコープ解決オペレータ

namespace N
{
	...
}

としたスコープ内すべてを、N 名前空間と呼び、この内部にあるすべてのクラスなどは、 グローバル名前空間や他の名前空間と区別され、また、他の名前空間からは内部が見えなくなる。

グローバル名前空間で定義されるグローバル変数 x と、N 名前空間において定義される変数 x は、 グローバル名前空間においては、

x;

とすると、グローバル変数 x を表し、

N::x;

とすると、N 名前空間の変数 x を表す。この :: オペレータをスコープ解決オペレータという。

一方、N 名前空間において、

x;

は、N 名前空間内で定義される変数 x を表す。したがって、N 名前空間内でグローバル変数 x を参照する場合、 スコープ解決オペレータを用いて、

::x;

と表現する。関数 N::m(); や、クラス N::C も同様の表現で解決される。

複数の同名の名前空間を定義すると、それらはすべてあわせて、1つの名前空間として認識される。

名前空間の入れ子

グローバル名前空間内に任意の名前空間が定義できるように、一般に名前空間内には、 ある名前空間を定義することができる。

namespace N1
{
	A a;
	namespace N2
	{
		B b;
		namespace N3
		{
			C c;
		}
	}
}

グローバル名前空間において、それぞれの名前空間は、スコープ解決オペレータを用いて、

N1
N1::N2
N1::N2::N3

のように解決される。

using 宣言と using ディレクティブ

N1::N2::N3::c のような名前空間内で定義されている変数があるとき、グローバル名前空間において、

using N1::N2::N3::c;

と宣言することで、以降 c はグローバル名前空間において可視状態になる。

したがって、もし、この時点で、グローバル名前空間内に c という名前のグローバル変数が定義されていると、 名前が衝突し識別できなくなり、エラーとなる。

また、名前空間 N2 において、

using namespace N3;

とすることで、N3 名前空間内のすべてを N2 名前空間において可視状態とすることができる。

文字列処理

std 名前空間と文字列クラス

C 文字列とは、'\0' 終端の char 型配列として定義される。意味的にはわかりやすいが、 取り扱いは非常に難解であり、文字列のコピーなどの基本的な操作でさえライブラリ関数に頼らざるを得ず、 また、配列である以上、その長さは基本的に固定である。

string ヘッダファイルにおいて、std 名前空間内で定義される、std::string は、 このC 文字列に対する、クラス化バージョンであると考えてよい。

C 文字列にあった欠点である、文字列のコピーなどといった操作の煩雑さや、非可変長であるというものを 改善したクラスである。

C 文字列と文字列オブジェクト

std::string は、const char* 型からのコンストラクトキャストを持つ。したがって、 C 文字列であれば、コンストラクタを経由することで文字列オブジェクトにキャストすることが出来る。

一方、std::string から C 文字列への直接のキャストは定義されない。C 文字列への代入は、 代入オペレータではなく、

char *strncpy(char *,const char *,size_t);

を用いなければならないため、std::string は、   

const char* std::string::c_str(void)const;

という、文字列リテラルを返すメソッドを用いる。

文字列リテラルとして使用する場合は、c_str() メソッドを直接用いればよいが、 C 文字列として文字列処理を行う場合は、strncpy() 関数を用いてC文字列を作らなくてはならない。

文字列オブジェクトのオペレータ

C 言語における直感的な文字列処理における関数が、オペレータとしてカプセル化される。

char *strncpy(char *, const char *, size_t);

は、文字列オブジェクトにおいて、代入オペレータ = として定義される。 右辺は、文字列オブジェクトでも C 文字列でも可能である。

char *strncat(char *, const char *, size_t);

は加算代入オペレータ += として定義される。 この意味において、加算オペレータ + が自然に定義される。

int strncmp(const char *, const char *, size_t);

は、比較オペレータ群 == や != に自然に関連付けられて定義される。

文字列オブジェクトのメソッド

C言語における文字列処理関数が、自然な形でメソッドとして再定義される。

size_t strlen(const char *);

は、

size_t std::string::length(void)const;

メソッドとして再定義される。

const char *strstr(const char *, const char *);

は、

size_t std::string::find(const string&);

メソッドとして再定義され、引数の文字列に一致する size_t 型の位置を返す。

const char *strpbrk(const char *, const char *);

は、

size_t std::string::find_first_of(const string&);

メソッドとして再定義され、引数の文字列中に含まれる文字に一致する size_t 型の位置を返す。

char& std::string::at(size_t);

として定義され、指定位置の文字への参照を返す。

例外処理

throw ステートメント

ある処理をしている途中において、なんらかの原因で想定している正常な状態と言えなくなったと 検知されたとき、処理を強制的に中断し、エラー処理を行わなくてはならない。

このときのエラーを 例外 といい、throw ステートメントは例外の発生をエラー処理を行う場所へと伝える。

throw exception;

とすることで、throw ステートメントは exception という例外を投げる。

try スコープ

throw によって例外が投げられたかどうかは、

try
{
	...
}

で指示される、try スコープ内でのみ検出される。したがって、例外が投げられる可能性がある部分は、 try で囲まなくてはならない。

catch() スコープ

ある try スコープ内の throw ステートメントによって投げられた例外は、try スコープの直後にある、

catch(...){
	...
}

のスコープで受け取られる。catch スコープ内で、この throw した時点で発生した例外に対して、処理を行う。

もし、try スコープで囲わなかったために、throw ステートメントから例外が投げられたことを、 catch スコープで受け取ることが出来なかった場合は、グローバル関数 void terminate() が呼び出され、 プログラムは強制終了される。

例外オブジェクト

std 名前空間と例外クラス

throw ステートメントはあるオブジェクトを例外として投げる。そして、例外が発生したことを伝えるために、 throw ステートメントによって投げられる専門のクラスが、exception ヘッダファイルにおいて std 名前空間内で定義される例外クラス、std::exception、およびその派生クラスであり、 それらのオブジェクトを例外オブジェクトという。

std::exception クラス

std::exception は、public なデフォルトコンストラクタを持つため、 std::exception() とすることで生成することができ、

throw std::exception();

とすることで、例外オブジェクトを投げる。

try スコープ内で例外オブジェクトが投げられた場合、それに続く catch() スコープにおいて、

catch(const std::exception& error){
	...
}

などとすることで、std::exception、およびその派生クラスのオブジェクトへの const 参照を error として受け取ることが出来る。

what() メソッド

例外オブジェクトを生成する場合、コンストラクタに const char*const&型変数を指定することが出来る。 ここで指定した変数は、catch()スコープ内において受け取った例外オブジェクトへのconst参照から const char* std::exception::what()const メソッドを呼ぶことで、参照することが出来る。

new オペレータと std::bad_alloc 例外

void *malloc(size_t) 関数は、ヒープ領域の確保に失敗すると、NULL を返す。 一方、new オペレータが インスタンスの構築に失敗した場合は、std::bad_alloc 例外を投げる。

問題となるのは、new オペレータを呼ぶ場所であり、例えば、コンストラクタ内で例外が投げられると、 その時点でコンストラクタで正常に構築が完成せず、したがって、デストラクタでも正常に解放できなくなる。

原則として、コンストラクタ内から例外を外部に投げてはならない。必ず、コンストラクタ内で投げられた例外は、 コンストラクタ内で受け取り、適切な処理を行わなければならない。

dynamic_cast<T> オペレータと std::bad_cast 例外

仮想クラス D の派生クラス C に対して、C 型オブジェクト c を D& 型 オブジェクト d が参照する状態、

D& d=c;

を考えるとき、この D& 型オブジェクトに対して、C& 型へのキャストは、

dynamic_cast<C&>(d);

で行う。もし、仮想クラス D の継承でないクラス T へのキャストを、

dynamic_cast<T&>(d);

と行うと、ダイナミックキャストは、不正なキャストを行ったとして、std::bad_cast 例外を投げる。

例外クラスの継承

標準で定義されない例外を定義する場合もまた、ある標準の例外クラスの継承 として定義することにより、const std::exception& として例外を受け取ることが出来る。

このように共通した例外を受け取ることも出来、また、それ以前において それぞれの継承クラスを 直接受け取るような catch() スコープを作ることで、処理を特殊化することも可能である。

論理例外と実行時例外

std::logic_error クラス

いわゆる論理エラーと呼ばれる類の例外基底クラスである。プログラムにおける構文エラーは、 コンパイラによって検出されるが、コンパイラによって検出されないプログラム上の論理的なバグは、 このような形で例外として検出しなくてはならない。

基本的には、バグであるため、この例外が発生するプログラムは望ましい結果を得ることが出来ないと考えられ、 この例外が発生することにより、プログラムが強制終了することは、可能な限りデバッグを行うことで 避けるべきものである。

stdexcept ヘッダファイルで定義される。

このクラスでは、コンストラクトキャストとして、const std::string を引数とするコンストラクタが利用できる。

std::out_of_range クラス

配列の有効範囲外へのアクセス時などに投げられるべき例外クラス。char& std::string::at(size_t)などが 有効範囲外を参照すると投げる。

std::invalid_argument クラス

意味的に不正な引数の指定時に投げられるべき例外クラス

std::length_error クラス

最大の長さを超える長さの値による処理を行った場合に投げられるべき例外クラス

std::runtime_error クラス

いわゆる実行時エラーと呼ばれる類の例外基底クラスである。仮に構文エラーも論理エラーもない場合においても、 プログラム実行時において、ユーザの入力ミスや、データの不正、あるいは、ハードウェア的な障害などで、 正常でない状態になることは避けられない。

したがって、このようなエラーが発生した場合には、例外処理として、実際にユーザにその例外発生を 伝えるような動作を行わなくてはならない。

stdexcept ヘッダファイルで定義される。

このクラスでは、コンストラクトキャストとして、const std::string を引数とするコンストラクタが利用できる。

std::range_error クラス

数値演算処理の結果、有効な値の範囲外となった場合に投げられるべき例外。

std::overflow_error クラス

数値演算処理の結果、オーバーフローが発生した場合に投げられるべき例外。

std::underflow_error クラス

数値演算処理の結果、アンダーフローが発生した場合に投げられるべき例外。


文章作成 : yukki-ts (-+-twilight serenade-+- [stage])