私にとっては驚愕の事実です.
なんと,ゴミ収集の時,ゴミ収集車が音楽を鳴らす地域と,鳴らさない地域があるようです.
私が一定期間住んだ事のある地域と言えば,大分県大分市(1978年〜現在まで在住)・国東市(1978〜現在まで断続的),そして東京都中野区(2001〜2007)・千代田区(2007〜2008)・大田区(2008〜2009)・港区(2009)ですが,音楽は一切聴いたことがありません.
こりゃ,聴くしかないんじゃないですか?
どこ? ヨコハマとかですか?
私にハマに住めと?(言ってない)
カブに乗って買い出しをしろと?(誰も言ってない)
あと……今ちょうど港区の東京タワーの前に住んでるんすけど,ゴミ,持ってってくれないんすけど…….
さて続きです.解決したい問題はこんなの:
Base クラスのコンストラクタで重要なリソースを確保しやがっているBase クラスを継承した Deriv クラスを作りたいDeriv の初期化のタイミングで,例外が発生する可能性があるBase クラスで確保したリソースをきっちり解放してあげたいのだけれど……これ,C++ ならば何も考えなくてよいのです.
class Base {
CriticalResource* iCr;
public:
// 今回に限っては意味無いけど,一応礼儀作法として
// function-try-block で包んでおいた方がよいでしょう.
// というか,本当はスマートポインタが推奨されます.
Base() try : iCr(0) {
iCr = new CriticalResource;
} catch (...) {
delete iCr; iCr = 0;
}
virtual ~Base() {
std::cout << "ちゃんと ~Base() が呼ばれた" << std::endl;
delete iCr; iCr = 0;
}
};
class Deriv : public Base {
public:
Deriv() {
throw std::exception();
}
virtual ~Deriv() {
std::cout << "~Deriv() は呼ばれない" << std::endl;
}
};
int main() {
Deriv* d = 0;
try {
d = new Deriv;
} catch (std::exception& x) {
delete d;
}
}
実際に実行すると
$ test.exe ちゃんと ~Base() が呼ばれた $ _
ほらね.
C++ では長らく「コンストラクタでの例外は鬼門」と信じられてきましたが,どういう場合に何が起こるのかを踏まえておけば,そうビビる必要もありません.
なお,ここで知っておきたいのは,delete d; にてデストラクタが実行されたわけでは無いという事.リソースを巧く破棄できたのは,「サブクラスのコンストラクタで例外が発生したら,それまでに生成されたスーパークラスのデストラクタが呼ばれる」という言語仕様のおかげです.
Delphi も C++ と同様,コンストラクタで例外が投げられるとデストラクタが呼ばれます.ただし C++ と違って (今手元に無いので確認できませんが) TDeriv のコンストラクタで例外が発生すると,TDeriv のデストラクタが実行されるんだったかと (そして必要ならば inherited を実行するんだったかと).
PHP でも,異常時以外は必ず呼ばれるデストラクタがありまして (なんかもういろいろ端折ってますが)
<?php
// PHP 5.9 で試しましたとさ.
class CriticalResource {
public function __destruct() {
echo __CLASS__.'::'.__FUNCTION__."が呼ばれた\n";
}
}
class Base {
private $cr_ = null;
public function __construct() {
$this->cr_ = new CriticalResource();
}
public function __destruct() {
echo __CLASS__.'::'.__FUNCTION__."は呼ばれないの?\n";
}
}
class Deriv extends Base {
public function __construct() {
parent::__construct();
throw new Exception();
}
public function __destruct() {
parent::__destruct();
}
}
/**
* 我らがメイン
*/
try {
new Deriv();
} catch (Exception $e) {
}
?>
実行すると
Y:\_test>php test.php CriticalResource::__destruct()が呼ばれた Y:\_test>_
おおっと,Base クラスのデストラクタが呼ばれたわけでは無いようです.しかしとにかく $cr_ の参照は解除され,きちんとリソースを破棄できました.
C# は,IDisposable と using の組み合わせは助けにはなりませんでした.やはり最後の頼みの綱「ファイナライザ」に頼る事も視野に入れるべきです.「GC が働く前にプロセスが終了してしまう場合」にはファイナライザが呼ばれないのであれば,それって結局 Java と同じですな.
class Base: IDisposable
{
private bool mDisposed = false;
public void Dispose()
{
Console.WriteLine("Base が Dispose された");
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (mDisposed) return;
if (disposing) {}
mDisposed = true;
}
~Base()
{
Console.WriteLine("Base のファイナライザが呼ばれた");
Dispose(false);
}
}
class Deriv: Base
{
public Deriv()
{
throw new Exception();
}
///
/// ここがメイン
///
public static void Main()
{
try
{
using (Deriv d = new Deriv())
{
}
}
catch (Exception e)
{
// ...
}
}
}
上記のコードでは,Base が Dispose されません.ファイナライザはプロセスが終了する頃に呼ばれたので,やっぱり Java 的な香りがします.
ruby や python でも「デストラクタ」の類はあるようですが,やはり Java や C# と同じく,積極的な使用は歓迎されないようです.
…… GC の戦略の性質によるもので,仕方なくデストラクタを歓迎しない=何かスマートな解法があればすぐにでも歓迎したいのか? それとも先に「デストラクタなぞキッシュ・イーターのオヤツだ」という思想があり,その思想にのっとって言語や GC をそのようにしたのか? という点に興味が出てきますけども.
しかし.そういえば,GC が動く前にプロセスが終了するって,どんな時なんだろう?
もしも,余程の異常事態でもない限りプロセス終了時に GC は必ず動き,ファイナライザやデストラクタが必ず呼ばれるのであれば,「呼ばれるか否か」の点では C++ のデストラクタと大体同じ信頼性とも言えるわけですね.C++ だって,メモリアクセス違反とかで OS の怒りを買って「落ちる」時は,デストラクタは呼ばれないわけだし.
Linux では,メモリ不足となると "OOM Killer" が働いて適当にプロセスを kill して周るのだとか.こんな事態じゃ,そりゃもちろんファイナライザなんて無理に決まってます.
お仕事などでコーディングする際には,そういう事も踏まえながら,「プログラマはどこまで責任をもってコーディングすべきなの?(完全・完璧・究極無敵な後始末は無理だよ)」を煮詰めていくのです.無理な部分は運用でカバー.
ここらへんの話は,またあとでゆっくり調べることにします.
あと,こういう問題,20 年くらい昔に MIT の IRC か USENET かどこかで議論されて「いや,Deriv が巧いことやればいいじゃん」とか結論が出ていそうな気がするんだけど,どう調べればいいのやら.
これは「クラスの設計思想が間違っている」で終わる話なので,以下の文章にはあまり厳密性が求められるべきではありません.別の言い方をすれば「行間を読んで」です.便利な言葉.
Java っていうか何ていうか,デストラクタのようなもの (Java では finalize()) が確実に呼ばれる保証の無い言語全般に言える事ではありますけども.ともかく,なんかつまんなさそうな問題を思いつきました.
/**
* こいつは abstract である.
* このクラスをサブクラス化し,必要なメソッドを実装して使うこと!
*/
public abstract class Base {
// 慎重に扱わなければならないリソース
private CriticalResource cr_ = null;
/**
* コンストラクタ
*/
public Base() {
cr_ = new CriticalResource();
}
/**
* この release() は,重要なリソースを
* しっかり解放するのである!
*/
public final void release() {
if (cr_ != null) cr_.release();
}
}
特に問題のないクラスだと思います.慎重にリソースを扱うコードと言えます.そう見えるでしょ?
次,Base クラスを継承した Deriv クラスです.
class Deriv extends Base {
/**
* コンストラクタ
*/
public Deriv() throws Exception {
// 別に重要なリソースを扱ってるわけじゃないけど
// 例外が発生する可能性のある処理
something();
}
/**
* そして我らがメイン
*/
public static void main(String...args) throws Exception {
Deriv d = null;
try {
d = new Deriv();
} finally {
if (d != null) d.release();
}
}
}
ああ何てこと.Deriv のコンストラクタが例外を投げたら,Base が持っている重要なリソース cr_ の release() を実行できなくなってしまいます.
なぜ?
finally の中で,変数 d は null だから,ですね.
Base クラスのコンストラクタは完了し,cr_ も生成されたとします.しかし Deriv クラスのコンストラクタで例外が発生すると,main() メソッドの中のローカル変数 d には何の参照も割り当てられないまま,つまり null のまま,finally 節へ突入する事になります.
Base クラスが強大なベンダ様の持ち物であり,ワタクシドモには中身を弄れないとすると,これを解決するにはどうすりゃいいんでしょうね.
// 基本的には,最後の頼みの綱として finalize() を信じるべきかと
class Deriv2 extends Base {
@Override
public void finalize() {
try { release(); }
finally { super.finalize(); }
}
}
// それとも,こう?
class Deriv3 extends Base {
/** コンストラクトだけは絶対に成功させる */
public Deriv3() {
}
/** こっちにも初期化用のコードを書く */
public void initialize() throws Exception {
something();
}
}
// こういう手もあり……?
class Deriv4 extends Base {
public Deriv4() {
boolean ok = false;
try {
something();
ok = true;
} finally {
if (!ok) release();
}
}
}
ええ,大体こんな感じで解決ですとも.でもなんとなく,あんましカッコよくない気が.
結構“やっちまう”問題だと思うんですよ.
例えば私はサーブレットを作っているとします.ブラウザから渡されたパラメータによって,DB から情報を検索するだとか,DB に情報を追加するといった,「アクション」が行われます.その「アクション」たちはどいつもこいつも,ブラウザからのパラメータを解析して,DB への接続を確立して,ユーザのログイン状態もチェックして……必ずそういう「共通の処理」があるわけで,「AbstractAction クラス」を作ってあげたくなるのが人情.
/*
* AbstractAction
*/
public abstract class AbstractAction implements Action {
// テンポラリファイル.
// 作ったくせに削除し損ねたら,始末書な.5ページな.
private TemporaryFile workArea_ = null;
// コンストラクタ
protected AbstractAction() {
workArea_ = new TemporaryFile();
}
// これを呼び損ねたら始末書!
public void release() {
if (workArea_ != null) workArea_.release();
}
public final void executeAction(
HttpServletRequest req, HttpServletResponse resp) {
// ブラウザからのリクエストを解析
/* ... */
// DBへの接続を確立
/* ... */
// ログイン状態チェック
/* ... */
// 各アクションを実行
action();
}
// 各アクションはこれを実装する
public abstract void action();
}
そんで
/*
* サーブレットは大体こんな感じ.
* なんかいい加減ですけども.
*/
public class MyServlet extends HttpServlet {
@Override
public void service() {
AbstractAction action = null;
try {
action = newAction( ブラウザからのリクエストによって,
生成するアクションを換える);
action.executeAction();
} finally {
// これを呼び損ねたら始末書...
if (action != null) action.release();
}
}
}
ほら.こういうパターン,結構ありますよね.
つ☆づ☆く
さて昨日の続きです.「デシジョンテーブル」という整理の手法がありまして,これは使いどころさえ間違えなければなかなか便利.
例えば,目の前にタマゴがあるか否かを判別できる機械があって,これとオーブンレンジを組み合わせて「タマゴをレンジで温めようとすると警告ブザーを鳴らす」製品を作りたいとします.大ヒット間違いなしです.どのように仕様を整理するとカッコイイでしょうか?
例えば,こんな感じ.
灰色の行で分けられていますが,上は条件,下は動作です.
電源が入っていないなら動きません.
電源が入っていて,オーブンモードで,中身が空っぽの場合,オーブンとして動作します.中身が空っぽでも,お菓子を作る時などで事前にこのようにして温めておくのは必須ですもんね.
でも電子レンジモードの場合,中身が空っぽだと動きません.意味ありませんし.
電子レンジモードで,タマゴが入っていれば警告ブザーです.他の動作はしません.
とりレバーが入っているだけなら,普通に動作します.とりレバーってレンジで温めると爆発するよねーと,小箱とたん先生も興味津々のご様子ですが気にすんな.
これに昨日紹介したマクロなどを設定してやり,さらに,条件や動作を書いている列に対してもう一工夫.こんな「条件付き書式」を設定してやります.「数式が」「=INDIRECT(ADDRESS(ROW(), $B$1)) = "●"」の場合に文字をちょっと目立たせるわけですね.
すると……
カーソルを動かすと……
さらに動かすと……
なんという事でしょう(アレ風).今注目している条件〜動作のセットが強調表示されるではありませんか.
カーソルの位置をシートの左上 A1:B1 の部分に書き出していますが,別にそうしなくてもいいんですよね.VB で書いたマクロを数式として呼び出す事も出来るわけだし,もうちょっとすっきりと工夫できるはず.
……と記事を書き終わった後でアンドリューさんという方の非常にシンプルな実装を発見してしまうわけで.人生なんてこんなもの.
この業界で仕事をしていると,Microsoft Excel や,それに類するスプレッドシート編集ソフトは何て便利なんだろうと感動する事があります.これ一本で設計書も仕様書も,テストデータも書けるし,結合試験以降のエビデンス──スクリーンショットなんかもこのシートに貼り付けてお客様に提出します.非常に強力なマクロで DB も好きにいじれるし,酷い時には,ちょっとした打ち合わせの議事録すら Excel で残そうとする オッチョコチョイ 愛好家もおらっしゃる程.
しかし Excel にはちょっと不満もありまして,広いディスプレイでカーソルを動かしていると,今,どの行・列にカーソルが居るのかが分からなくなってきます.スプレッドシートの上部「A」や左部「100」のようなトコロはかろうじて強調表示してくれるのですが,もうちょっとなんとかならないかと.というよりも,現在カーソルのあるセルの上下・左右をまるごと強調表示してくれないかと.
簡単なマクロと「条件つき書式」で実現させました.まず目的のシートに,こんなマクロを埋め込みます.現在カーソルがある位置を,シートの左上に書き込んでいるわけですね.
Option Explicit
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim v(2)
v(0) = Target.Row
v(1) = Target.Column
Me.Range("A1:B1").Value = v
End Sub
次に条件付書式.シート全体を選択し,次のように設定します.
第一条件は「数式が」「=ROW() = $A$1」の場合に網がけを.第二条件は「数式が」「=COLUMN() = $B$1」の場合に網がけです.セル自身の位置と先ほどマクロで書き込んだカーソルの位置が関連するならば網がけをするわけですね.
するとこんな表示に.見事,カーソルの上下左右に色がついています.
そして,もうちょっとピリリとスパイスを効かせる技もありまして.また明日.
大量の“やりたい事”以下略。つまり 9 月は Win32API をいじっていたかと思えば,今日は Java をいじっていたりするわけで。
こんなデッドロックをやってしまいました。
// Deadlock1.java
public class Deadlock1 extends Thread {
public static void main(String...args) throws Exception {
Deadlock1 d1A = new Deadlock1();
Deadlock1 d1B = new Deadlock1();
// お互いを参照します.
// (本当は,少なくとも片方は WeakReference にするべき)
d1A.another_ = d1B;
d1B.another_ = d1A;
// 片方のスレッドを開始します.
d1A.start();
// もう片方はここ(メインスレッド)で動かします.
d1B.run();
// 無事に処理が終わればいいのだけども...
}
Deadlock1 another_;
@Override
public void run() {
for (int i = 0; i < 10; i++)
run2();
}
synchronized void run2() {
// 自分のではなくて,相方の work メソッドを動かします.
another_.work();
}
synchronized void work() {
try {
for (int k = 0; k < 5; k++)
Thread.sleep(1); // 仕事は寝ること(わらぃ
} catch (Exception e) { }
}
}
メソッド全体を synchronized で覆った上で run2() と another_.work() に分けて呼んでしまったので,なぜこれでデッドロックが起きたのかを追いにくかったのですが,冷静に整理してみると,上の Deadlock1 クラスの内容は下記 Deadlock2 クラスと同じです (main() は省略).
public class Deadlock2 extends Thread {
Deadlock2 another_;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
// ここでデッドロック発生!!!1123
synchronized (this) {
synchronized (another_) {
another_.work();
}
}
}
}
void work() {
try {
for (int k = 0; k < 5; k++)
Thread.sleep(1); // 仕事は寝ること(わらぃ
} catch (Exception e) { }
}
}
this と another_ を,この順番でロックしています.つまり d1A インスタンスは d1A の後で d1B を,d1B インスタンスは d1B の後で d1A をロックしようとしていたのです.ここまで整理すれば,これは本当に典型的なデッドロック.めでたしめでたし.
さてと,明日も仕事で朝が早いので,そろそろ寝ることにします.
ネンマツネンシ? 何ですかそれは?
大量の“やりたい事”がとうとう詰まってしまい,時間が流れません.助けてください.
この雑記を更新するのはかなり久しぶりですが,C++ や Win32 API と戯れるのも若干久しぶり.それらに出会ってもう10年以上経ちますが,まだまだ,知識やスキルを疑われそうなポカから逃れる事は出来ません.
// SetProp 用の文字列です.
#define SELF_PTR _T("SELF PTR")
/*!
* 「サンク」というテクニックを使わない場合の一般的なフレームクラス
* もしかしたらコンパイル通らないかも知れないけど,今回,細かい個所はあまり重要じゃないので...
*/
class CWrongFrame {
protected:
/*!
* WNDCLASSEX::lpfnWndProc に設定するウィンドウプロシージャ
*/
static
LRESULT __stdcall windowProcedure(HWND aHwnd, UINT aMsg, WPARAM aWp, LPARAM aLp) {
// ウィンドウハンドルにに this ポインタが関連づけられている場合
// handleMessage() 関数に処理を委譲します.
CWrongFrame* self = static_cast<CWrongFrame*>(
reinterpret_cast<void*>(
::GetProp(aHwnd, SELF_PTR)));
if (self) {
return self->handleMessage(aMsg, aWp. aLp);
}
// まだ self が存在しない場合,ここで設定しています.
// 普通は WM_CREATE の時に処理すれば充分なのですが,
// 今回はちょっと気分を変えて,WM_CREATE よりも前に飛んでくるメッセージである
// WM_NCCREATE の時に処理してみました.
switch (aMsg) {
case WM_NCCREATE: {
CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(aLp);
self = static_cast<CWrongFrame*>(cs->lpCreateParams);
::SetProp(aHwnd, SELF_PTR,
reinterpret_cast<HANDLE>(self));
}
break;
default:
break;
}
return ::DefWindowProc(aHwnd, aMsg, aWp, aLp);
}
private:
HWND iHwnd; //< ウィンドウハンドルをメンバとして保持します
private:
/*!
* メッセージを処理します
*/
LRESULT handleMessage(UINT aMsg, WPARAM aWp, LPARAM aLp) {
/*
* WM_CLOSE の時や WM_DESTROY の時の決まりきった処理は略
*/
// 自分で処理するつもりのないメッセージは DefWindowProc に丸投げです
return ::DefWindowProc(iHwnd, aMsg, aWp, aLp);
}
public:
/*!
* コンストラクタ
*/
CWrongFrame(): iHwnd(0) {
/*
* ここで WNDCLASSEX を登録する作業・・略
*/
// いよいよウィンドウを生成します.
iHwnd = ::CreateWindowEx( /* 引数略 */ );
}
};
もし同じような経験のある方なら,ここで既に「あー(わらぃ」と感じているかも知れません.一歩ずつ解説してみます.
まずコンストラクタ.CreateWindowEx() でウィンドウハンドルを生成し,メンバ変数 iHwnd に格納します.とてもよくありがちなコードですが,この発想から若干の間違いを含んでいたとは,この時はまだ夢にも思っていないのでした.
CreateWindowEx() の最中,いくつかのメッセージが windowProcedure() に飛んできて処理されます.
WM_NCCREATE が飛んできた時,まだウィンドウハンドルにオブジェクトを関連づけていないので,変数 self は NULL のまま.switch の分岐に到達します.
switch (aMsg) {
case WM_NCCREATE: {
CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(aLp);
self = static_cast<CWrongFrame*>(cs->lpCreateParams);
::SetProp(aHwnd, SELF_PTR,
reinterpret_cast<HANDLE>(self));
}
break;
第4引数 aLp を CREATESTRUCT* へキャストし,lpCreateParams を取り出してウィンドウハンドルに関連付けています.このデータとは,実は CreateWindowEx() の最後の引数に入れておいた this ポインタ.別に驚く事も無いですね.常套手段です.
ここで一旦,ウィンドウプロシージャから抜けます.
さあ次のメッセージが飛んできました.ウィンドウハンドルに this ポインタが関連づけられたので,次のメッセージからはローカル変数 self が NULL でなくなり,self->handleMessage() の中で処理されますね.
LRESULT handleMessage(UINT aMsg, WPARAM aWp, LPARAM aLp) {
/* ... */
// 自分で処理するつもりのないメッセージは DefWindowProc に丸投げです
return ::DefWindowProc(iHwnd, aMsg, aWp, aLp);
}
問題はここなのです.実はまだ CreateWindowEx() からは抜けていません.すなわち,iHwnd の値はゼロ.ここに WM_NCCALCSIZE だったか何だったか,とにかく重要なメッセージがたくさん飛んで来るのに……
こうすれば動くわけですね.まずウィンドウプロシージャの中では
switch (aMsg) {
case WM_NCCREATE: {
CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(aLp);
self = static_cast<CWrongFrame*>(cs->lpCreateParams);
self->iHwnd = aHwnd;
::SetProp(aHwnd, SELF_PTR,
reinterpret_cast<HANDLE>(self));
}
break;
そしてコンストラクタでは
CWrongFrame(): iHwnd(0) {
HWND hwnd = ::CreateWindowEx( /* 引数略 */ );
// この hwnd はエラーチェックにしか使わなくていいや
}
さてー,こんなミス,初めてではない気がしています.前にも同じ事をやらかしたような…….学習しないオトナってのは,こうもタチが悪いもんですかね.←他人事
CreateDialog にも対応したいなら,WM_INITDIALOG についても気にするのがプロ.