もののついでで「超」関係ないお話。 去年SNKさんがGoogleグループでサクラエディタ開発に興味のある人を集めたときに、MIK氏を名乗る方をお見かけました。ハンドルが少し違う名前でしたが、お気に入り機能の開発者当人だと言ってたのでMIK氏だと思います。「最近使ったファイル」をサクラエディタに導入し、各種ダイアログにヘルプチップ表示機能を組み込んでくれたあのMIK氏です。 いまメンバーじゃない人は、もうプロジェクトの貢献者じゃないんでしょうか。 いま、sakura-editorプロジェクトのメンバーリストにMIK氏はいません。いないんですけど、sfのプロジェクト管理画面で見たら「参加希望者リスト」に名前があるのかも知れません。ないのかも知れません。ぼくは完全に外の人なので、内部のことは分からないです(^^; いまプロジェクトメンバーでないことと、過去の貢献がどれだけ素晴らしいものだったかということは全く関係がないような気がします。 3年くらい前、sourceforge.netから多くの開発者が離れたときに、安全を考えてアカウントを削除した開発者も結構いたんじゃないかと思っています。ぼくはアカウントを残しましたが、アカウントを消した人のことを責める気にはなりません。...
よくよく考えてみるとコンストラクタだけ「例外禁止」にする意味はない気がしてきました。結局のところ、例外禁止と言ってもバグで例外投げちゃう可能性はあるわけで、それだったら「noexcept推奨です」といったほうが分かりやすいのかもです。msvcがnoexceptに対応するのはvs2015からですが、それ以前のvc++でもdeclspecでnothrowを指定すれば「似た感じの効果」を得られます。noexceptを指定した関数はアンワインド情報を生成しないので例外を投げた場合はterminateされます。コンストラクタをnoexceptにするのは例外禁止にするのとほぼ同じ意味になります。 これを信用していないんです。 それを言ったらですね・・・(^^; 記憶が確かなら「共有メモリは~するまで使えない」というコメントが随所にあった気がします。処理内容はコメントでなくコードで表現する、という原則からするともうちょっと考えないといけないのかも知れませんが、コメントくらいあったような気はしています。この辺はできるだけ触らないようにして、変える必要がある場合は何人かでレビューしてやってくしかないのかな、と思っています。(現状を考えると、既に詰んでる発想ですけどね。)...
よくよく考えてみるとコンストラクタだけ「例外禁止」にする意味はない気がしてきました。結局のところ、例外禁止と言ってもバグで例外投げちゃう可能性はあるわけで、それだったら「noexcept推奨です」といったほうが分かりやすいのかもです。msvcがnoexceptに対応するのはvs2015からですが、それ以前のvc++でもdeclspecでnothrowを指定すれば「似た感じの効果」を得られます。noexceptを指定した関数はアンワインド情報を生成しないので例外を投げた場合はterminateされます。コンストラクタをnoexceptにするのは例外禁止にするのとほぼ同じ意味になります。 これを信用していないんです。 それを言ったらですね・・・(^^; 記憶が確かなら「共有メモリは~するまで使えない」というコメントが随所にあった気がします。処理内容はコメントでなくコードで表現する、という原則からするともうちょっと考えないといけないのかも知れませんが、コメントくらいあったような気はしています。この辺はできるだけ触らないようにして、変える必要がある場合は何人かでレビューしてやってくしかないのかな、と思っています。(現状を考えると、既に詰んでる発想ですけどね。)...
どうして処理をコンストラクタに移したいと考えたのかは理解してもらいたいと考えています。 これに関して確認ですが、 現状のコードが正しい、という結論に至ったという理解で良いんですよね? 現状のコードは、共有メモリの初期化をする前に、具象クラスの固有識別名を使って排他制御用のミューテックスを取得しています。共有メモリの初期化コードをコンストラクタに移動すると、その部分がミューテックスの保護領域から外れる結果となります。 コードの見通しをよくする試み自体を否定したいわけじゃありません。 責任の放棄であり間違いの元 GetShareData というアクセサを定義しているのは CProcess だから、それを通してアクセスする CShareData が InitShareData 済みの有効なオブジェクトであることを保証するのは CProcess の仕事だということです。 何が言いたいのか、なんとなく理解しました。 しかし、CProcessはメインスレッドで作成するシングルトンオブジェクトで、CProcess::Run()する前にCProcess::GetShareData()が呼ばれることはありません。CProcess::Run()は先頭でCProcess::InitializeProcess()を呼び出します。具象クラスのInitializeProcessはCProcess::InitializeProcess()を呼出す前に自クラスの固有識別名を使ってミューテックスを取得します。ミューテックスを取得している間のコードは、システムレベルでアクセス競合が発生しません。CProcess::InitializeProcess()の中でCShareData::InitShareData()が呼ばれます。CProcess::InitializeProcess()がtrueを返した以降は、CProcess::GetShareData()はInitShareData済みのCShareDataを返すようになります。よって、具象クラスがCProcessの継承ルールを守っている限り、CProcess::GetShareData()の呼出しで問題が起きることはないように思います。...
とりあえず、コンストラクタが失敗するとマズい場合の実例です。 以前投稿したメモリリークが発生するパターンを検証可能なかたちで示すものです。 ここのプロジェクトが公式にサポートする開発環境(=vc++2005)で作成しています。 実行すると、コンストラクタの中でメモリ不足例外がスローされます。 プログラム自体はtry~catchで例外を無視するので正常終了します。 メモリリークチェックを有効にしているので、 プログラム終了時に解放されなかったメモリの一覧がダンプされます。 ちゃんとメモリリークしますよね? コンストラクタで例外を投げられるようにするには、この例で示した実害が実際には発生しないこと、または発生してても無視できる程度であることを証明する必要があると思います。そのうえで、例外を投げられるようにした場合のメリットを示してください。
「コンストラクタで例外投げるの禁止」に対しての反論なら コンストラクタで例外を投げるメリットを例示してください。 例外投げたらまずい場合があるんだけど切り分けがめんどくさそうだから全面禁止にしませんか?と提案しています。たしかに「こうすれば大丈夫」な方法はいくつか存在しますが、ぼくが思いつく対策はどれもかなりめんどくさいです。関数tryブロックも超めんどくさい対策案の1つです。例外スローを許容して対策が漏れた場合、かなり見付けづらい種類の潜在バグになります。どうしてもコンストラクタで例外を投げたいのであれば、これらのデメリットが霞んでみえるくらいのメリットを提示する必要があると思います。 関数 try ブロックを使えば この方法は、採用することそのものに障害があると思っています。 現状のsakuraコードベースには関数tryブロックを使ったものはありません。 手元環境で確認する限りvc++2003以降であれば使えますが、入っていません。 ここのプロジェクトが、かなり長いことvc++6でビルドできることにこだわり続けていたらしき経緯は把握しています。そのことの是非は脇に置いておいたとしても、15年に渡って不要と判断され続けてきた特殊記法を、今更導入できるとは思えません。知らない人が見たら「catch書く位置違うくね?」と思うのが関数tryブロックなので。...
CProcess が行う CControlProcess/CNormalProcess の共通部分の初期処理を CProcess::InitializeProcess から CProcess のコンストラクタへ移したのは技術的な理由です。 こう書いたら良いんではないでしょうか? bool CNormalProcess::InitializeProcess() { //CNormalProcess固有の初期化処理(共通の初期化処理に先立って実行) ... //CProcess派生クラスに共通する初期化処理 bool ret = CProcess::InitializeProcess(); if (!ret) return ret; //CNormalProcess固有の後続処理 ... } 派生クラスのオーバーライドメソッドから基本クラスの同名のメソッドが呼び出せることは、派生クラスの仕事が基本クラスの仕事をベースとしつつデコレーションすることであれば役に立ちます。しかし失敗できない必須の処理をオプションとして派生クラスに押しつけていたのは責任の放棄であり間違いの元です。 ここで言ってることが難しくてよくわからんかったです。...
サクラエディタのコードの中には、通常は失敗しない処理がコケた時にメッセージを表示するコードがちらほらあると感じています。 通常は失敗しない処理がコケた時・・・ まさに例外の使いどころだと思いますが、 普通にif(...){正常処理}else{メッセージ表示;return;}になってるところが結構あります。 たぶん、あるべきは↓な感じにして例外を投げることだと思います。 throw std::exception( "異なるバージョンのエディタを同時に起動することはできません。"); メッセージ表示は、呼出元でtryして拾ってあげればよいのです。 実際には、投げる例外のクラスはどうだとか、多言語対応はどうすんだとか、関係ない例外を間違って拾ったらどうすんだとか、込み入った話題がぐちゃぐちゃありそうに思えますけど。 もちろん、例外を投げるようにする場合、引用部分のコードをコンストラクタに移動するのは先に述べていた理由で反対です。個人的には、共有メモリを作成/参照する処理は、プロセス抽象クラスの「初期化」にあたると思います。コントロールプロセスにしてもエディタプロセスにしてもインスタンス化した...