関数 try ブロックを使えば基本クラスのコンストラクタが例外を投げた場合でも、コンストラクタの仮引数には安全にアクセスできます。コンストラクタに new int した結果や HWND が渡されていてコンストラクタ本体が実行されなかった場合でも、それらを解放してから例外脱出するチャンスが用意されていました。
pszA1 = new char[MAX_PATH];
pszA2 = new char[MAX_PATH]; //ここで例外が起きる
delete pszA1;
delete pszA2;
こういう輩がいるから new を書かせてはいけないんです。不幸にしてこういうコードが存在しているから、それを前提にして例外を投げないようにしようということでしょうか。つまり一切の例外を投げるなということですよね。むしろコンストラクタで例外を投げる場合はそれをメンバに持つクラスのコンストラクタが実行されないわけですから、struct _tagA のメンバオブジェクトのコンストラクタが例外を投げることはメモリリークにつながらない。
例外は投げませんよ。しかしそれでもコードの置き場所にこだわっておられるようなので、まだ書いていなかった、というか書いたけどお蔵入りさせていた補足説明を以下に貼ります。
CProcess が行う CControlProcess/CNormalProcess の共通部分の初期処理を CProcess::InitializeProcess から CProcess のコンストラクタへ移したのは技術的な理由です。つまり、基本クラス、派生クラスのコンストラクタ同士の関係のように、派生クラスの InitializeProcess が呼ばれたときには CProcess が責任を持つ共通部分の初期処理が完了していることを保証したかった。そうすれば派生クラスからの明示的で漏れやすい初期化要求が不要になり、また、CProcess が管理する共通部分の有効性確認を派生クラスに義務づけなくても済む。そのために都合がよかったのがコンストラクタです。
CProcess::InitializeProcess というメソッドを以前の通りに、しかし private メソッドとして残しておくこともできました。そして派生クラスのオーバーライドメソッドが呼び出すのではなく CProcess::Run メソッドが、派生クラスの InitializeProcess を呼び出す前に CProcess::InitializeProcess が成功裡に実行されていることを保証する。しかしそうすると CProcess::InitializeProcess というメソッドの二重性が従来のまま残ります。CProcess 自身にとっての初期処理としての位置づけと、派生クラスに向けたテンプレートメソッドとしての位置づけです。
派生クラスのオーバーライドメソッドから基本クラスの同名のメソッドが呼び出せることは、派生クラスの仕事が基本クラスの仕事をベースとしつつデコレーションすることであれば役に立ちます。しかし失敗できない必須の処理をオプションとして派生クラスに押しつけていたのは責任の放棄であり間違いの元です。
二重性の解消と、共通の初期処理が成功していることを派生クラスに保証することがコンストラクタへの初期処理の移動の理由です。それが唯一の手段ではありませんが、もうひとつメンバ関数を増やしたり、InitializeProcess メソッドを二重利用したりすることを避けるとそうなりました。
以上の理由により CShareData::InitShareData が共有メモリの確保と初期化を一体的に行うためには Mutex で守られている必要があるようです。その場所が CControlProcess::InitializeProcess です。CNormalProcess の場合は確保済み・初期化済みの共有メモリを取得することしか想定していないため特別な考慮をしていないのでしょう。危なっかしい期待ですが。
読み込み不足のまま軽率なことをしましたが、おかげで気がつくことができました。ありがとうございます。時間の無駄ですから r1_1 パッチは無視してください。CProcess::InitializeProcess の二重性と実行タイミングはそのまま保存せざるをえないでしょう。
共有メモリの初期化とコントロールプロセスの初期化を切り離した、危なっかしくない3者の作業分担もあるはずですが、あえて手を加える理由もありません。コントロールプロセスにのみ存在するCShareData::m_pvTypeSettings なんて頭の痛いメンバもあり、うかつに手が出せません。
CFileNameManager::GetIniFileName にはCShareData::InitShareData に本来置かれるべき初期化コードが埋め込まれていて、その pszProfName 引数は初期化時以外は無視されます。関数シグニチャからの期待に反して、プロファイル名を指定して対応する ini ファイル名を取得する関数ではないんですよ。それができるのは一番乗りさんだけ。これも頭が痛い。
こう書いたら良いんではないでしょうか?
bool CNormalProcess::InitializeProcess() {
//CNormalProcess固有の初期化処理(共通の初期化処理に先立って実行)
...
}
ここで言ってることが難しくてよくわからんかったです。
失敗できない必須の処理とか、責任の放棄とかのあたり。
initializeProcessは、メッセージループに入ってよいかどうか(成功or失敗)を返すメソッドなので、処理結果は成功か失敗になります。
メソッド設計の「あるべき姿」に照らして考えれば、このメソッドは処理要求と状態取得を同時に行っているのであまりよいメソッドとは言えません。モダンなオブジェクト設計手法で作りなおすとすると、初期化を行うvoidメソッドとメッセージループに入って良いかどうかを返すboolプロパティに分けるべきなのかも知れません。ただ、そういう話はしていませんよね?失敗できない必須の処理、というのが謎だと思っています。
責任の放棄について。オブジェクト設計手法だとクラスとは単一の責務を表すオブジェクトであると定義することができます。CControlProcessにはCControlProcessの責務があり、CNormalProcessにはCNormalProcessの責務があります。派生元クラスCProcessの責務は、CControlProcessとCNormalProcessに共通する責務であるはずです。クラスは、与えられた責務を果たすためにメンバ変数を持ちます。通常、メンバ変数の初期化・設定はクラスの責務の一部です。protectedメンバは派生クラスからも参照・設定できますが、クラスを責務で分けて考えた場合には、派生クラスが親クラスのメンバを変更するのはルール違反です。もし親クラスがそのメンバの初期化方法を知らない場合、そのメンバは親クラスにあるべきではないのかも知れません。そういう場合、virtual Type* getInitialXXX()= 0;を用意して派生クラスに初期化を強要することになります。派生されることを前提にしたクラス(=抽象クラス)の責務は定義が難しいです。書かれていることを読む限りだと責任の放棄ってのとはちょっと違うような気がしました。
関数 try ブロックを使えば基本クラスのコンストラクタが例外を投げた場合でも、コンストラクタの仮引数には安全にアクセスできます。コンストラクタに new int した結果や HWND が渡されていてコンストラクタ本体が実行されなかった場合でも、それらを解放してから例外脱出するチャンスが用意されていました。
またしても berryzplus さんが主張するコンストラクタが例外を投げるべきではないことの根拠がわからなくなりました。
メモリ上に直接 virtual な(※実質的に存在するという意味です)オブジェクトを配置するのではなく、まずはCスタイルでメモリをビット列としてとらえ、メモリの確保と初期化(※オブジェクトとしての存在開始)は別だという考えなのでしょうか。それはスタイルですから否定できませんけどね。
「コンストラクタで例外投げるの禁止」に対しての反論なら
コンストラクタで例外を投げるメリットを例示してください。
例外投げたらまずい場合があるんだけど切り分けがめんどくさそうだから全面禁止にしませんか?と提案しています。たしかに「こうすれば大丈夫」な方法はいくつか存在しますが、ぼくが思いつく対策はどれもかなりめんどくさいです。関数tryブロックも超めんどくさい対策案の1つです。例外スローを許容して対策が漏れた場合、かなり見付けづらい種類の潜在バグになります。どうしてもコンストラクタで例外を投げたいのであれば、これらのデメリットが霞んでみえるくらいのメリットを提示する必要があると思います。
この方法は、採用することそのものに障害があると思っています。
現状のsakuraコードベースには関数tryブロックを使ったものはありません。
手元環境で確認する限りvc++2003以降であれば使えますが、入っていません。
ここのプロジェクトが、かなり長いことvc++6でビルドできることにこだわり続けていたらしき経緯は把握しています。そのことの是非は脇に置いておいたとしても、15年に渡って不要と判断され続けてきた特殊記法を、今更導入できるとは思えません。知らない人が見たら「catch書く位置違うくね?」と思うのが関数tryブロックなので。
個人的には今後も使わない方向で構わないと思います。
つまり、関数tryブロックを使わないとキャッチできない例外の投げ方はしない、ってことで、コンストラクタから例外を投げるべきじゃない、に繋がります。
参考ですが、ちらっとググったときに、比較的信頼できるc++erさんによる関連記事を見かけました。例外指定に関する記事ですが勉強になります。
https://cpplover.blogspot.jp/2010/08/blog-post_03.html
簡単に対策できるんだからいいじゃん、
という認識で書いたコメントなら論破できたと思います。
関数tryブロックの導入は容易じゃないと思います。
というかそもそも、何が起こるかちゃんと伝わってない気がしてきました。
使ってる用語が微妙に的を得ていないのが気になりますが、
メモリ確保とオブジェクト生成を別に考えてるんじゃないですか?
と聞かれればyesです。
c++でnew ClsA();と書くと
new演算子でメモリ確保された後にClsAのコンストラクタが呼ばれます。
スタイルや思想ではなくて、高級言語c++の泥臭い実態です。
javaのようにnewが失敗したら勝手にgcされるようなわけにはいかんのです。
検証できるサンプル作って見るので少々お待ちを・・・。
とりあえず、コンストラクタが失敗するとマズい場合の実例です。
以前投稿したメモリリークが発生するパターンを検証可能なかたちで示すものです。
ここのプロジェクトが公式にサポートする開発環境(=vc++2005)で作成しています。
実行すると、コンストラクタの中でメモリ不足例外がスローされます。
プログラム自体はtry~catchで例外を無視するので正常終了します。
メモリリークチェックを有効にしているので、
プログラム終了時に解放されなかったメモリの一覧がダンプされます。
ちゃんとメモリリークしますよね?
コンストラクタで例外を投げられるようにするには、この例で示した実害が実際には発生しないこと、または発生してても無視できる程度であることを証明する必要があると思います。そのうえで、例外を投げられるようにした場合のメリットを示してください。
まとめて返答します。
つまり元の通りですね。共有メモリの確保と初期化を一体的に扱うためにはその手順が必要なことがわかったので、CShareData に関してそうすべきだという点を争うつもりはありません。ただそういう問題がなければ、どうして処理をコンストラクタに移したいと考えたのかは理解してもらいたいと考えています。
GetShareData というアクセサを定義しているのは CProcess だから、それを通してアクセスする CShareData が InitShareData 済みの有効なオブジェクトであることを保証するのは CProcess の仕事だということです。すべての派生クラス(具体的には CControlProcess/CNormalProcess)に対して「確保と初期化が一体的に行われるようになんらかの調停手段を講じたうえで CProcess::InitializeProcess を呼んでくださいね。そうしないうちに GetShareData を呼んで CShareData を操作すると何か良くないことが起きるけど、私は知りませんよ」という CProcess の態度について述べています。
struct _tagA のコンストラクタに注目すればよいでしょうか。これ、コンストラクタ関係ないですよね。new の扱いが雑だからメモリリークしている。それだけのことです。コンストラクタの失敗がまずいのではなく、ただのまずいコンストラクタです。そしてこのまずさはコンストラクタに固有のものではない。ただこれがまずいというだけの話です。
こういう輩がいるから new を書かせてはいけないんです。不幸にしてこういうコードが存在しているから、それを前提にして例外を投げないようにしようということでしょうか。つまり一切の例外を投げるなということですよね。むしろコンストラクタで例外を投げる場合はそれをメンバに持つクラスのコンストラクタが実行されないわけですから、struct _tagA のメンバオブジェクトのコンストラクタが例外を投げることはメモリリークにつながらない。
サクラエディタのコードは例外を前提としていないから例外を投げることは良くない、という主張であれば否定することはできません。コンストラクタは関係ありませんが、そうなんでしょうか。
Last edit: ds14050 2018-04-28
これに関して確認ですが、
現状のコードが正しい、という結論に至ったという理解で良いんですよね?
現状のコードは、共有メモリの初期化をする前に、具象クラスの固有識別名を使って排他制御用のミューテックスを取得しています。共有メモリの初期化コードをコンストラクタに移動すると、その部分がミューテックスの保護領域から外れる結果となります。
コードの見通しをよくする試み自体を否定したいわけじゃありません。
何が言いたいのか、なんとなく理解しました。
しかし、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()の呼出しで問題が起きることはないように思います。
問題を起こせる可能性があるとすればCProcessのシングルトンインスタンス経由でCProcess::GetShareData()をアクセスするグローバル関数を書く場合でしょうか。共有メモリを直接アクセスするグローバル関数を微改善する方策としての需要がありそうに思っていますが。
まったくその通りです。
リソースの扱いが雑だからリソースリークするんです。
途中でthrowするのがまずいのではなく、ただのまずいコードです。
はい。
まずいコードと正しいコードの切り分けは困難なので、
コンストラクタでは一切の例外を投げないようにしましょうと言っています。
いいえ。
今後は積極的に例外を活用していくべきだと考えています。
一度書きましたが、CShareData::InitShareData()の失敗は例外でハンドリングするのがc++化した処理の本来の姿なんじゃないかと考えています。
現状は生ハンドルを直接扱うコードが相当量ありますから、
例外を導入してコードの見通しをよくする試みには障害が多いです。
これを信用していないんです。CControlProcess/CNormalProcess に手を入れる将来の人間、また第3のプロセスクラスを書く人間にそのルールを守らせるものがコードではなくコメントですらない。※その信用ならない人間が私です(反省)
自分について述べますと、これまで try/catch/throw を書いたことがありません。デバッグ時にコーディングエラーを捕捉する目的で assert を書く(間接的に例外を発生させる)程度です。数日前に実験して知ったのですが、引数を指定しないで例外を発生させる throw; は catch(...) でキャッチできないのですね。これは驚きでした。そして throw; といっても同じ例外を再スローする意味の場合はまた違うのでしょう。
Last edit: ds14050 2018-04-28
よくよく考えてみるとコンストラクタだけ「例外禁止」にする意味はない気がしてきました。結局のところ、例外禁止と言ってもバグで例外投げちゃう可能性はあるわけで、それだったら「noexcept推奨です」といったほうが分かりやすいのかもです。msvcがnoexceptに対応するのはvs2015からですが、それ以前のvc++でもdeclspecでnothrowを指定すれば「似た感じの効果」を得られます。noexceptを指定した関数はアンワインド情報を生成しないので例外を投げた場合はterminateされます。コンストラクタをnoexceptにするのは例外禁止にするのとほぼ同じ意味になります。
それを言ったらですね・・・(^^;
記憶が確かなら「共有メモリは~するまで使えない」というコメントが随所にあった気がします。処理内容はコメントでなくコードで表現する、という原則からするともうちょっと考えないといけないのかも知れませんが、コメントくらいあったような気はしています。この辺はできるだけ触らないようにして、変える必要がある場合は何人かでレビューしてやってくしかないのかな、と思っています。(現状を考えると、既に詰んでる発想ですけどね。)
第3のCProcessクラスについては、CEditWndを表示せずに終了するパターンが切り出せると思っています。Grepダイアログとか複数ファイルオープンとか、エディタを表示しない場合のコードをクラスごと分離してやればCNormalProcess::InititalizeProcessが少しすっきりしそうです。
新人教育とかでjava教えてる現場にいくと
catch(Throwable t)にAssertErrorが引っかかるのを見て驚いてるのを見かけることがあります。
assertで発生した例外をキャッチして何かしようとしたらいかんです。
もちろん何かしようとしたわけではないと思いますが・・・
試してみました。ぬるぽ状態になってCRTがエラーを吐いている感じです。
throw;は再スローを指示する式文なので、実行時のコンテキストに既にスローされた例外がないとダメみたいです。なんか、コンパイルエラーにしてほしい気分なんですが、あえてthrow;を通すことで得られるメリットがなんかあるっぽいので・・・。
C++例外ではなく、構造化例外処理(SEH)でやればエラーを補足できます。
デバッガで止めてcode = 0xe06d7363を拾うことができました。
https://support.microsoft.com/ja-jp/help/185294/prb-exception-code-0xe06d7363-when-calling-win32-seh-apis
//#include <excpt.h>
//C構造化例外フィルタ関数(codeに例外コードが入る)
int filter(unsigned int code, struct _EXCEPTION_POINTERS *ep) {
return EXCEPTION_CONTINUE_SEARCH;//次のエラーハンドラを探せ、という意味の戻り値。
}
//これはc++関数
void test1( void ) {
throw;//ここでre-throwする
}
//これはC関数(Structured Error Handling利用)
void main( void ) {
try {
test1();//C関数ではC++例外をスローできないので関数を呼び出す。
} except(filter(GetExceptionCode(), GetExceptionInformation())) {}
}
--
修正
throws;→throw;
補足
code = 0xe06d7363(error of msc)が示すのはmsvcrtによってC++例外がスローされている、ということです。フィルタに渡された例外情報を覗いてみると、re-throwモードが指定されているのに元例外情報がNULLという変なデータが渡ってきていることが分かります。catch(...)で捕捉できないのはこのためと考えられます。
Last edit: berryzplus 2018-04-29
誤解を招く命名だと思った CProcessFactory::IsStartingControlProcess を IsBeingControlProcess に改名したことの根拠が整理できたので書きます。
このメンバ関数は自プロセスがコントロールプロセスとして起動しようとしているかどうかを判定します。これを英語で表現しようとして以前の人や自分が考えたのが次の作文です。
誤解して次のように読みました。
微妙です。メンバ関数名からは不定冠詞が省略されているので実質自他の区別がつきません。その区別がここでは重要です。
Last edit: ds14050 2018-04-19
いちおうレスつけときます。
気付いてませんでした。
確かに変な名前ですね。
…待て待て、それってIsStartingじゃねーだろ?みたいな。
プロセスのファクトリで使うメソッドなので
タイミング的には間違いなくstartingなんですが、
調べてるのはstartingかどうかじゃないわけで。
難しく考え出すとはまりそうですが、具体的なコマンド指定があるかどうか調べるメソッドなのであれば、指定文字列そのものを名前に含めてやると分かりやすい気がします。
単にIsNoWin。
名前がかぶるのが嫌ならIsNoWinRequestedとか。
IsNoWinRequiredでもいいかも知れません。
要求された、指定された、指示された…そんな感じの言葉を組み合わせてやれば意味が通ると思います。
すべてに難癖をつけるようで恐縮しています(本当)。
この部分に対して自分が感じていた違和感は、それは手段であって関数の目的ではないだろうということです。
つまりこの述語関数が述べたいことは、自プロセスの成り行き、CNormalProcess を体現するのか CControlProcess を体現するのかということであり、その手段が可視のウィンドウが要求されているかどうか(-NOWINオプションの有無)を確かめることなのだから、関数の説明としては不適当だということです。
コメントを関数名に寄せるべきなのです。IsNoWinRequested というコメントに寄せた名前では CCommandLine::IsNoWindow の単なる焼き直しであって、意味解釈の階梯をこれっぽっちも上っていないのです。
Last edit: ds14050 2018-04-19
まず伝えないといけないのは、すべてに難癖をつけているわけじゃないということです。
ぼくがスルーすることで「誰も文句ないんだよね?」という状況になるのを避ける目的で、本当に問題のありそうなものにだけレス付けてます。
ぼくも意外と忙しい人なので暇つぶしに難癖付けてるわけではないんです。
その証拠にCSakuraProcessはスルーしてますし、昨年末の置換の話もスルーしました。
問題なさそうなら黙認します。
是非やるべきだと思うパッチには賛同コメントいれます。
難癖つけるのは本当にヤバくね?と思ったときだけです。
IsNoWinRequestedという提案については、ぼくの認識あやまりです。
ここのプロジェクトのメソッド定義に関する意識の高さを甘く見ていました。
そこについては弁解の余地もなく、大変失礼な発言だったと考えています。
おっしゃる通りオブジェクトクラスのメソッドには、処理内容ではなくメソッドの目的を体現する名前をつけるのがオブジェクト志向設計の定石です。IsStartingControlProcessのように抽象的な目的を表す名前にしておけば、コントロールプロセス起動を表すパラメータが変わったときにメソッドの中身を変えるだけで簡単に対応できるという効果があります。
ただ、それを踏まえて考えると、
CProcessFactory::IsStartingControlProcessは適切な名前に思えてきます。
the process factory is starting a control process.
プロセスファクトリはコントロールプロセスを起動しようとしています。
the process factory is being a control process.
プロセスファクトリはコントロールプロセスになろうとしています。
ぼくも作文はあまり得意じゃないんですが、
どっちがメソッドの目的を表しているか、説明は要らない気がします。
誤解ですよ。「あーいえばこーいうでお前何にも俺の意見聞かへんやんけ」と思われてやしないかと危惧しているのは自分です。
もうひとつ誤解。やっぱり説明は必要です。
IsStartingControlProcess は「コントロールプロセスを起動しようとしています」と言っているのではありません。CControlProcess を作成することでコントロールプロセスになろうとしているんです。ただ、これだと主語が CProcessFactory ではなくなってしまってるんですよね。しかしコントロールプロセスが自プロセスなのか他プロセスなのかの区別はできる。それが重要だとはもう書きました。
Last edit: ds14050 2018-04-20
startには他動詞としての用法があります。
https://ejje.weblio.jp/content/start
Web辞書で見るとだいぶ後ろのほうに載ってるので
あまりメジャーな用法でないのかも知れませんが、
「~を開始する」という意味に使われることがあります。
クラスメソッドを述語関数という場合、述語に対する主語はクラスになります。
だから「ファクトリが」「コントロールプロセスを」「開始しようとしている」で意味が通ると言っています。
ファクトリはプロセスオブジェクトを作るためのものです。
「作る」なのでCreateとしたいところですが、
CreateProcessだと他プロセスの起動と混同されそうです。
windowsにはスタートボタンが「ある」ので、
あえてstartにしたのかも知れません。
違いますね、自他両方の用法があることは最初から分かっていてそう書いてることに気付きました。
ならば、ファクトリクラスが他プロセスを起動するかどうか考えてみてください。
プロセスファクトリはプロセスオブジェクトは作りますが、プロセスを作るファクトリではありません。なので開始されるコントロールプロセスが他プロセスである可能性はゼロです。
どうしても気になるようであれば、プロセスファクトリをやめる方向の検討をすすめるのも1つの手かと思います。
プロセスという名前のクラスを「作る」ためのファクトリを用意してしまったことが問題なんじゃないかというアプローチです。
そうでしょうか。IsStartingControlProcess 改め IsBeingControlProcess は
するか
するかの分岐点です。StartControlProcess が新プロセスを開始します。
一晩経ってみたら浮かびました。似た案を見た記憶があります(すっとぼけ)。
本当だ。ボケてました。すみません。
金曜飲みがえりだったのを言い訳にします。
なんだろう、この違和感。
以下は元のコードです。
最初の部分のIsStarting~をIsBeing~にしたほうがいいって話ですよね。
IsBeing~を判定したあとの処理が2択じゃないのが違和感の正体なのかな。
newしない場合がありますし、startしない場合があります。
分岐の意図はたぶん、ControlかNormalのどっちをnewするか判定することなんじゃないかな。
コントロールプロセスが存在しないとエディタプロセスをnewできないかというと、そうじゃないはずで、newするための依存関係がごちゃごちゃしてるクラスは面倒くさいわけで。分岐の中のnewするコード以外は、本来はCProcess::InitializeProcessの中でやる処理な気がするわけです。
そう考えるとnewするかstartするかの2択ではないから違うんじゃない?ということになるわけです。
酔っぱらってないのに勘違いしてた馬鹿が開き直るの図(^^;
これはひとり言です。古いコードを見ていると新鮮な発見があります。
https://sourceforge.net/p/sakura-editor/code/215/tree//sakura/trunk/sakura_core/CProcess.h
このときの CProcess::Run が呼ぶ3つのメソッドは =0 で純粋仮想とされておりテンプレートメソッドであることが明確でした。
https://sourceforge.net/p/sakura-editor/code/215/tree//sakura/trunk/sakura_core/CNormalProcess.cpp#l63
CNormalProcess::Initialize 関数で CShareData m_cShareData; DLLSHAREDATA m_pShareData; の2変数を m_cShareData.Init() などとして初期化していますが、この変数は Initialize 関数のローカル変数です! CShareData のデストラクタで ::UnmapViewOfFile されるのに共有メモリの寿命は???と首をひねりましたが、CNormalProcess のメンバ変数である CEditWnd m_pcEditWnd; もまたメンバに CShareData m_cShareData; を持っており、こちらでも m_cShareData.Init() を呼んでいました。サクラエディタの全プロセスを通して2回目以降の CShareData::Init は大した仕事をしないし、CShareData 自体がアクセス関数を提供するだけの軽量な被せ物なんですね。
そして当時から変わらないのは、明らかに CControlProcess::Initialize が呼び出す CShareData::Init の処理内容の想定と、CNormalProcess::Initialize や CEditWnd::CEditWnd が呼び出す CShareData::Init の処理内容の想定が異なるのに、それを CShareData::Init というひとつの名前に代表させて想定外のことが起こることをまるで警戒していない点です。CEditWnd::CEditWnd は特に油断しています。
https://sourceforge.net/p/sakura-editor/code/215/tree//sakura/trunk/sakura_core/CEditWnd.cpp#l132
CShareData の方でも共有メモリの確保から初期化完了までの待ちに関して下駄を呼び出し側に預けているのは同じで、自分はそういうクラスの境界をまたいだ約束事というものが不安で不安でしかたがない(笑) 自分が信頼に足る人間ではないから、同じように他人の確かさが信用できないんです。
以上、ひとり言でした。
もののついでで「超」関係ないお話。
去年SNKさんがGoogleグループでサクラエディタ開発に興味のある人を集めたときに、MIK氏を名乗る方をお見かけました。ハンドルが少し違う名前でしたが、お気に入り機能の開発者当人だと言ってたのでMIK氏だと思います。「最近使ったファイル」をサクラエディタに導入し、各種ダイアログにヘルプチップ表示機能を組み込んでくれたあのMIK氏です。
いまメンバーじゃない人は、もうプロジェクトの貢献者じゃないんでしょうか。
いま、sakura-editorプロジェクトのメンバーリストにMIK氏はいません。いないんですけど、sfのプロジェクト管理画面で見たら「参加希望者リスト」に名前があるのかも知れません。ないのかも知れません。ぼくは完全に外の人なので、内部のことは分からないです(^^;
いまプロジェクトメンバーでないことと、過去の貢献がどれだけ素晴らしいものだったかということは全く関係がないような気がします。
3年くらい前、sourceforge.netから多くの開発者が離れたときに、安全を考えてアカウントを削除した開発者も結構いたんじゃないかと思っています。ぼくはアカウントを残しましたが、アカウントを消した人のことを責める気にはなりません。
掲示板にあったsf.netとの共存期間の書込みをみて、ふとそんなことを思いました。