by antonin
検索
最新の記事
記事ランキング
タグ
雑感(302)
雑談(151) 妄想(126) ニュース(96) 散財(77) web(65) おバカ(59) 検索(54) 親バカ(45) 日本語(41) PC(40) 季節(39) 昔話(35) 信仰(31) 政治経済(29) イベント(27) 言語(25) 音楽(24) 言い訳(22) ビール(15) 以前の記事
最新のコメント
ライフログ
ブログパーツ
ブログジャンル
|
久しぶりにウィークエンド・プログラミングなんかをしている。業務ではなかなか手を出しにくいテンプレート周りの記法を試しに利用しているが、C++03時代には細かいところで高度なバッドノウハウを要求されて挫折していたことが、C++11では概ね思ったとおりに書ける方法が用意されていたりして、なかなかいい感じになっている。そもそも今まではunique_ptrすらなかったわけで、tr1::shared_ptrだとかboost::scoped_ptrがあるよと言われても、sharingしたくなかったりboostの複雑なconfigureに悩まされたりしたくなかったりすると、結局自分でスマートポインター作りから始めるなんていう状況が多かった。 最近、まだ新しいPCの電源部がぶっ壊れ、借り物のPCにCygwin+GCCを入れたりなんかしたついでに、整数型を完全にテンプレートの向こうに追いやって素数計算なんかをするとどんな感じになるか、なんてことを試して遊んでみた。C++11の制定から2年以上経過して、GCCもだいぶ安定して「カバのダンス」もうまくなったらしいので、-std=c++11で新しい機能を少し試しながら書いてみた。今回、テンプレートの中でstd::vectorだとかstd::unique_ptrだとかを使ってみたが、autoだとかtemplate aliasなんかがとても便利で、いままではテンプレート中なのにCっぽく泥臭い記述でお茶を濁さざるを得なかった表現が、ずいぶんスッキリと書けるようになっていた。 次に、Brainfuckの実装をstrategyパターンっぽくして、命令実行エンジンの最適化レベルとか、メモリ部のモデルとかに依存しないように実装してみることにした。まずはソースファイルのオープンでしょう、ということで、設定オブジェクトがコマンドラインから拾ってきたソースファイル名でリードオープンするクラスを作っていたのだが、さて、コンストラクタでオープンさせるのか、別途メンバ関数のOpenを呼んで成功チェックするかどうか悩んでいた。古典的には、コンストラクタは本当に例外的な事象以外では成功するようにしておいて、エラーの予期される初期化にはそれ用のメソッドを呼ぶべき、という作法があった。 が、現代的なC++の思想では基本的にRAIIで、普通のブロック中だろうが別のクラスの初期化リストの中だろうがポインタを取る演算式の中だろうが、どこでも完全なオブジェクトを生成するか、あるいは例外を投げて正しく失敗しなければならない。そうすれば、別の誰かが例外をブン投げたりしてスコープを外れた場合は必ずお行儀よくデストラクトする仕組みが言語レベルで提供されている。であるからには、きっとC++11にはそういうスタイルのための基本的な道具立てはそろっているはずだと思い、イディオムを探してみた。すると、案外混乱している様子が見て取れた。 コンストラクタでの例外はあり?なし? - Togetterまとめ 河野 真治さんが意外と古風な論を展開していて驚いたが、実用的な実装を重ねてきた経験からの意見なんだろう。が、週末プログラマーとしては憚ることなく安っぽい理想論を展開していきたい。週末プログラミングは保守的な実務プログラミングでのフラストレーションを晴らす意味もあるので、あんまり現実的なのは嫌だ。しかし実際問題として、オンデマンドでオブジェクトを生成するRAIIに対して例外処理を安直に書くと、文法上の障壁があって困っていた。下のリンク先にとても上質な議論があって、その中に言いたいことがずばり書かれていた。 C#のvarとtry~catchが糞すぎる - やねうらお-俺のやねうら王がこんなに弱いわけがない。 (第2期) using (var sr = new StreamReader(path)) そうそう。これこれ。上記のコードはC#のサンプルだが、C++11でも同様のスコープの問題がある。まずリソース確保のためにオブジェクトを確保するのだが、それをtryブロックに入れてしまうと、その後のコードから参照できず、せっかくコンストラクトが成功してもtryブロックを出るときにデストラクトされてしまう。馬鹿かと。アホかと。 結論としては、例外を投げて失敗する可能性のあるコンストラクタを呼んで、しかも失敗時にリカバリーできる失敗なのだとしたら、スタック上に実体で受けるんじゃなくてヒープへのポインタで受けて、tryブロックの外にあるスマポに入れればいいじゃない、ということになる。オブジェクト本体はコンストラクタで例外を投げるとしても、スマポを使えば、そのコンストラクタはno_throwだし、newしたポインタの登録が初期化になるわけで、スマポの使用を前提とすれば、結局は失敗の可能性の高い初期化とオブジェクトの定義を自由に分離できる。だから、ポインタの先にあるプリミティブなクラスの実装がコンストラクタで例外を投げても全く問題ないということになる。 コーノさんは基底クラスのコンストラクタが例外を投げるようだと継承しにくいという問題を挙げていたが、他の人が述べていたように、インターフェイスと実装が分離されていないクラスを継承によって再利用しようとすること自体に何らかの設計ミスがある、という考え方のほうが(現実的にはともかく理想的には)正しいのだと思う。レガシーシステムに組み込むような事態を考えないで済むなら、コンストラクタは積極的に例外を投げていいと思う。コンストラクタにno_throwが必須の汎用クラスもあれば、no_throw制約が馬鹿げているような特殊リソース管理クラスもあって然るべきだろう。まあ、そういう場合もno_throwなデフォルトコンストラクタを持っているのは有用だろうけれども。 ただ上記引用の本論はvarで型情報を受けること、C++11で言うところのautoで受けるようなところが問題の核心なので、またちょっと別の話になるのだけれど。そのあとのコメントにあったouterみたいなのはいいと思ったが、利便性のためにブロックの機能を破壊するよりも、カジュアルに関数なりクラスなりを作って、外部から見て適切な振る舞いとなるようにラッピングしていくのが正攻法なんだろう。そのために静的なドキュメントを膨大に書かされるようなことのない現代的な開発環境であれば。 感想として、なんだかC++もだんだんCommon LISPみたいになってきたな、と思う。文法上の道具は充分に用意してあるから、文句があったら自分の使いやすいC++ベースのドメイン言語を自分で構成しろと。まあマクロありのS式みたいな構文上の柔軟性はないけれども、演算子のオーバーロードから始まって、C++14/17に向けてエキスパート向けの変態ツールがますます豊富になってくるみたいだから、JSF++みたいにエキスパートチームが先にコアライブラリなりフレームワークなりを定義して、アプリケーションプログラマはそのドメイン言語の範疇でプログラミングしていくことになるんだろう。 ただ、エキスパート不在の現場だとか、エキスパートたちの意思統合が下手な現場だとか、エキスパートの思想を末端のプログラマまで浸透させるだけの言語能力に欠ける現場だとか、そういう場合にはISO標準化以前のC++に匹敵するくらいマズい状況も予想されるだけに、ちょっと恐ろしい。LLVMが組み込み用に普及するような時代になると、もうC++の出番はないんじゃないかというような気もしないでもないが、まあ将来のことはわからないので、眠れなくならない程度にC++の進歩を見守っていきたい。
by antonin
| 2014-02-22 15:48
|
Trackback
|
Comments(0)
|
ファン申請 |
||