ぬうぱんの備忘録

技術系のメモとかいろいろ

作った曲一覧はこちら

ブロックでオブジェクトの寿命を制御

なにがあった

 はるか昔に見たC++のブロックの存在意義に今になって気づいたのでメモ。

そもそもブロックって

 ブロックっていうのは、関数内で

void hoge(){
	int foo;

	...

	{
		int bar;

		...

		cout << foo << endl;	//OK
		cout << bar << endl;	//OK

		...

	}

	...

	cout << foo << endl;	//OK
	cout << bar << endl;	/NG

	...
}

みたいに文字通り処理のブロックを作ることができるやつです。ifとかforとか、制御構文の時にいつもお世話になってますね。

このブロックは別にifとかなんとかがなくても書くことができて、ちゃんと意味もあります。
で、その意味を"変数の通用範囲が変わる"というスコープ的なことだと思ってたんですが、大きな勘違いだった様子で。

何はともあれコード

次のコードを見てください

#include <iostream>

using namespace std;

class CHoge{
private:
	const int _Val;

public:
	CHoge(int val):_Val(val){
		cout << "CHoge : " << _Val << endl;
	}

	~CHoge(){
		cout << "~CHoge : " << _Val << endl;
	}
};

int main(void){
	CHoge Hoge1(1);

	cout << "start block" << endl;

	{
		CHoge Hoge2(2);
	}

	cout << "end block" << endl;	

	return 0;
}

コンストラクタとデストラクタでメッセージ吐くだけのクラスを作ってるだけですね。ただし、mainではブロックを作っているという事です。
で、このプログラムを動かした結果は

CHoge : 1
start block
CHoge : 2
~CHoge : 2
end block
~CHoge : 1

ってなるわけです。Hoge(1)の方はmain関数いっぱいが生存期間で、Hoge(2)はブロック内いっぱいが生存期間のようです。それだけです。
ブロックってただのオマケ機能みたいなものだと思ってたでござる。

で、何に使えるのか

個人的にはこの事かなり衝撃だったのですが、何が衝撃的だったかって"ブロック閉じればデストラクト"が関数内で出来るんです。

C++標準のstd::ofstreamとかboostのmutex::scoped_lockなんかは、コンストラクタで初期化し、デストラクタで解放処理を行います。一応、close()とかunlock()とか解放処理を行う関数は用意されていますが"インスタンスが生存=インスタンスは有効"な方が単純明快でわかりやすくていいに決まってます。だって、この等式が成り立たないと"このオブジェクトは処理順によっては無効な事があるから気をつけよう"みたいな余計な懸念事項ができてしまいますから。

で、この"生存=有効"な状況をブロックを活用することで作り出せるわけです。ついでにブロック外からは変数を参照できないので"無効なオブジェクトを参照して実行時にエラー"みたいなことは起こりません。そもそもコンパイルエラーです。

このブロックによる生存期間の制御は、さっきも例に上げたミューテックスのロックがとっても綺麗に書けます。だって"ブロックの中にいる=ミューテックスをロックしている"に出来るんですから!