みねこあ

mineko. A! ―from mi-neko online.

2008-10-26

[]OOPと自動メモリ管理 23:41 OOPと自動メモリ管理を含むブックマーク

Simula は サブルーチンの不自由さに対し改善を施すことで、クラスとオブジェクトを発明しました。その副作用として、コールスタックによる自動メモリ管理が使えなく(←「コイツ使えね~っ」の「使えない」)なってしまいました。


* * *


「階層的プログラミング構造」Ole-Johan Dahl, C.A.R Hoare より引用しつつ。

Simula が シミュレーションのための言語を模索する過程で クラス・オブジェクトの発明に行きいきさつは、

平行しているプロセスを表現するために, 対応するプログラム要素が,計算機で多重プログラム(multiprogram)処理されなければならない,というわけではない.しかし,プログラムは,一時的に停止(中断)し,後で止まっていたところから再び実行出来ることが必要である.そこで動作している対象,すなわちシミュレーションにおける“プロセス”は,スケジュール機構 (時間割り当ての機構,scheduling mechanism) の制御のもとで,擬似的に平行して(in pseudo-parallel)働く (半) コルティンによって表現されるであろう.

がしたいので、ブロック(サブルーチン)といったものに、

新しいブロックの実例を作り出すプログラムでは,制御が戻ってきたときにには,その実例は消滅しているので,“もの”を持って存在している対象として,それと相互に作用し合うことは決してできないという欠点がある.(中略)つまり,ブロックの演算的な側面が強調されすぎているのである.

SIMULA 67 では, ブロックの実例はそれを呼ぶ出す文より長い間存在することが出来るし,プログラムでそれを参照する必要のある限り存在し続けることが出来る.

という改良を施しちゃった。コレが クラスとオブジェクトの始まり。

でも、ブロック(サブルーチン)間の構造が、コール/リターン (↓)じゃなくなっちゃったから、

最近に起動されたブロックの実例が最初に消滅するという意味で,ブロックの実例の存在が入れ子構造をなす事を保証するように留意して言語の規則が設計されている.このことによって, ALGOL 60 の処理系では, 動的に記憶場所を割り当てて解放する方法としてスタック(stack)を用いることが出来る

コールスタックによるいかした自動メモリ管理もつかえなっちゃった。

だから自動メモリ管理の主役は、コールスタックからガベージコレクタに移ったのです。そんなわけで Simula な本を読むと、OOP とガベージコレクタは 切っても切れない中だと思えてしまう。

そう、

SIMULA では,コルティンは,クラスの1つの対象で表される.

と、こんな感じ。(注:「対象」とかいて「インスタンス」と読む)


* * *


さて、そういう Simula な OOP なるものに対して、「コールスタックで頑張ろう!」「手動メモリ管理だって、むしろ本物のプログラマには実用的だ」と 改善をほどこした C++ と スッポスッポせんせは 凄いと思います。ここは痺れて憧れる所。

C++ の面白い点はまさにそこで、だから コールスタックなメモリ管理が実行されるタイミングにフックが仕掛けられるようになっていて、そこら辺をプログラマの意志でカスタマイズできてしまう。それがテンプレートと共にメタプログラミングの土壌になるのですが、――って、話がそれちゃった。

なので、C++ はガンキャノンからキャノンを取ってしまったくらい、ものすごい変わり種のOOPLだと思うのだけれども、そういう認識じゃなくって「代表的なOOPLの一つ」として敷衍してしまったのは、本当に不幸なことだとも思います。


――なぁんてことをだらだら書いてしまったのは、先日 新人の子に「C ではガベージコレクタがないので、昔のプログラマは全部手動でメモリ管理してたのですね!超人ですね!」みたいな事をいわれてギョッとしたからです。いあいあ、Cプログラマだって やたらめったら malloc してるわけじゃありませんから。

Cの構造化部品は関数だからコールスタックがうまくいくので、Cプログラマは 9割方(もっと?)自動メモリ管理のお世話になりながらプログラミングしています。でもC++は新しい構造化部品は導入したけれど それ用の自動メモリ管理機構は導入しなかった。だからメモリ管理の道具がCと同じ コールスタック + 手動ヒープ管理 であっても、同じような楽さ/大変さだと考えてはいけないっていうのがミソで、 C++ のような殆ど手動のメモリ管理な世界は、Cなプログラマからみても超人的な所行です。



蛇足

なんか思い出しました。

きむら(K)きむら(K) 2008/10/27 00:17 >動的に記憶場所を割り当てて開放する方法として
これ、原文から「開放」になってますか?
「解放」と「開放」じゃ文章の意味が変わっちゃいますが。

ところで夏目友人帳のDVD vol.1 は買われましたか?

minekoaminekoa 2008/10/27 00:28 >解放
うわ、写し間違いました。なおしました。ありがとうございます。

>友人帳DVD
もちのろんです。

実は間違えて2枚ほど買ってしまいました。(返品できましたが)
もう、どれだけ欲しかったのだか(w

vtwinautomatonvtwinautomaton 2008/10/28 01:45 速度が無視できるとこはスマートポインタ使いまくってでもサクサク書きつつ、速度重視のとこはコードもデータも全部L1/L2キャッシュに乗せる!それが真のC++プログラマだ!…多分…

minekoaminekoa 2008/10/29 21:23 >コードもデータも全部L1/L2キャッシュに乗せる
なぁんてコードは全く書いてません。やっぱり私ぁ似非のC++プログラマっぽいなぁ。

というわけで、おひさしぶりです。

せっかくですので近況報告!某案件では、割り込みを有効にすると、まれにキャッチ出来るはずの例外がキャッチできない不具合にお悩み中です。ピーンチ。

田辺田辺 2008/10/30 22:52 いつも質問ばかりですいませんが

> という一文が生理的に受け付けなかったので

どの点が受け付けられなかったのかわかりませんが、
抽象化しすぎたものは理解が困難になる、
というのはあると思うのですが、いかがでしょうか。

上級者に限らず、具体例があるとわかりやすい、
とか具体的なものが抽象的なものよりわかりやすい
ということがあると思います。

> あ、あと、共通化と抽象化は別のものだ

私自身、共通化と抽象化の違いをあまり意識していません。
もし、気が向いたらで結構ですので、
その違いと重要性についてご説明頂けたら幸いです。
その違いを認識していなかったとき、どのような不都合があるかとか。。

vtwinautomatonvtwinautomaton 2008/10/30 23:31 データサイズがキャッシュに乗りきった途端に、相転移の如く速度がでることとかは、まぁ時々ありますかね。最近私はCellのおかげで256KBとの戦いを繰り広げることも。256KBってDOS以下かよ!

> 例外がキャッチできない不具合

割込みがまずいなら、割込み禁止にすればいいじゃない…とかそんな簡単にいかないか (^^;
割込み専用スタックが無いから、通常スタックの例外あたりを壊してるのかなぁ。

田辺田辺 2008/10/31 06:20 > 上級者に限らず、
訂正、上級者においても、でした。

minekoaminekoa 2008/10/31 09:54 vtwinautomaton さん

例外の話は
http://www.windriver.com/japan/support/newsletter/tech_tip/tech_tip2518.html

に関連する様子。詳しくは調べられてないのだけれども、A6pci7503 のBSPであると大丈夫で、ACP104 なときは ダメっぽいのも怪しい感じです。(実は作ってもらった 割り込みハンドラ処理も ACP104 では動かなかったり(ハンドラ中で例外ベクタにエントリポイントを登録するようなコードがあるとアボート))

minekoaminekoa 2008/10/31 11:18 田辺さん

引用した一文はどうにでも読めてしまうものなのですが、つまりわたしの深読のしすぎかもしれないのですが、それでも頭の中でアラームが「危険!危険!」とガンガン鳴るのを止められません。虫の知らせとでもいうべきかしら。真の護身が完成したのかもしれません。

そう感じるのは多分いろいろな要因があって、一つは凪瀬さんがトラックバックしてくれたような話とか(この話の延長にはおそるべき「関数禁止令」- http://d.hatena.ne.jp/minekoa/20080209/1202577998 が控えています。恐ろしや..)、「抽象化し、共通ルーチン化しすぎたプログラム」とはすなわち単純に抽象化に失敗してるのでは?とかとか。なんか論理的に説明できていませんが、総じてデスマーチの香りが漂ってくるので背筋がゾワゾワしてしまいます。


▼共通化と抽象化の違い

まずは簡単な方から。

プログラミングでの「抽象化」とは、ヒトの脳の規模への脆弱性を補う為のテクニックです。一塊のコードにたいし、それの本質的な意味を解りやすく表現する名前を与え、それに置き換えることで、見かけのコード規模が小さくすることが出来ます。これにより、処理の難易度に関わらず単に規模が大きい(行数が多いetc)だけで、とたんに「理解」が出来なくなる人間の脳のポンコツな部分を補い、コードの可読性を上げます。

プログラミングにて抽象を実現するには、構造化エンティティ(メイヤー先生の言うところのモジュール、含むサブルーチン)に プログラムを封じその概念を示す名前をつけます。つまり構造化という手段を用いて抽象化という目的を実現します。

一方で、構造化という手段は、プログラム部品の共通化にも使われます。構造化されたプログラム部品は再利用可能ですので。

従って、たとえ一カ所からしか使われていないコードであっても、抽象化の為に関数やクラスに切り出します。一方で現状でたとえ同じ処理をしていたとしても、その本質が異なる処理を共通化してしまうことは、抽象化には値しません。極端な例ですと、アセンブリ言語な時代に、プログラムをメモリに収めるために本質的に全然関係のないコードを再利用する、なんてのがあったと思います。「抽象化しつつ共通化」が常ではなく、「抽象化だけ」「共通化だけ」という状況は少なからず存在します。

抽象化は可読性向上のため、共通化はメモリを節約したり、処理を再利用するため。「共通化だけ」では可読性は上がりません(むしろ下がります)。

つまり「構造化」という手段で、「抽象化」と「共通化」という異なる目的がたまたま混じっちゃったんだけれども、もともと全く異なることだよ、ということを頭の片隅に置いてプログラミングするは大切なことだよ~、と言う話です。



ここからは田辺さんの質問からは脱線する話ですが、元記事で話題に掲げられている、「共通化しておけば、修正も一カ所」、つまり変更に強いコードを作ることは、さらに別事だと意識することも大切に思えます。このケースでは、たいてい抽象化と共通化が同時に成されていますが、それだけでは足りなくて、それ以上――開放閉鎖原則に従った括りだしが求められます。この論点で語るなら、DRYの原則を無視して 同一コード片をちりばめたプログラムと、誤った括りだしをしまったプログラムは、同等に開放閉鎖原則に従っていませんから、どっちもダメ、というところからスタートすべき話だと思っています。

開放閉鎖原則にしたがった構造化の見極めが難しいシチュエーションでは、わたしは JavaBlack さん派――「設計やらない」ではなく「正しい設計にトライ」する人です。それに、「変更に強いコード」が出来なくても、せめて可読性の高い(抽象化された)コードを作るのは、意味のあることだと思います。

ただ、それがお仕事において良いことかはあんまり自信をもって言えないですね。わたし自身 未熟なプログラマだから、実際やった結果間違っていて手間が水泡に帰すこともままあります(^^;

ただ、常に正しくあろうとしてないと、容易に堕落してしまう わたしの生来のだらしなさを補う上ではまぁ、いいかな?、と。



▼抽象化しすぎたものは理解が困難になる / 上級者においても、具体的なものが抽象的なものよりわかりやすいケース

これは難しい。

上記のとおり、なぜ抽象するかは 「コードの規模がでっかい (エディタ一画面に収まらない関数とか)だけで読みにくい」を改善し、読みやすくするための物なので、もし抽象化しないほうが解りやすいのなら、抽象化する必要はないです。

けれど、具体的なものが抽象的なものよりわかりやすいケースがわたしにはピンと来ないので、何となく単に抽象化に失敗しただけの話じゃないかな、と思ってしまうのです。

たとえば、「具体的 vs 抽象的」 というのはちょっと何かがズレている感覚があります(上手く説明できませんが)。抽象化する、というのは「曖昧に言う」とか「広く言う」と言うことじゃなくって、「本質を抽出する」ということなので、本質を簡潔にズバズバ表現してくれたほうが、解りやすいんじゃないかな~と思います。その上で、確かに 初めて出会う概念を学ぶのに「具体的にいうと何なの?」というのを知りたくはなりますが、単にクラスや関数の定義を見に行けばいいだけですので、「だから 抽象化しない」にはつながらないように思えます。

「抽象化しすぎる」という状況もよくわかっていないです。「名前付けした概念にたいし、名前が適切でない」状況を抽象化しすぎたといっているんじゃないかとか、単にコードを括りだしただけで抽象化できていないものを抽象化しすぎたといっているんじゃないかとか。よくわからないので、そんな風に考えてしまいます。

んーー、なんだかわたし、ピントのずれた事を言っているような気がビンビンしております。

田辺田辺 2008/11/01 00:50 丁寧な解説ありがとうございました。
いまだ両者を厳密には区別できませんが、わかりかけてきました。
実装継承なんかは、共通化になるのでしょう。

> 抽象化する、というのは、「本質を抽出する」

私は、抽象化については、「本質」というものをあまり意識してません。
観点によって「それまでになかった“新たな”抽象(共通)概念を創造する」という感じです。
だから、抽象的になればなるほど、それを理解するには語彙が必要になってきて、
語彙のない人は、具体的なものの方がわかりやすい。

> けれど、具体的なものが抽象的なものよりわかりやすいケースがわたしにはピンと来ないので、

簡単にいえば、次のようなことです。

> 初めて出会う概念を学ぶのに「具体的にいうと何なの?」というのを
> 知りたくはなりますが、単にクラスや関数の定義を見に行けばいいだけですので

慣れない抽象概念は、具体的なものよりわかりにくいと。

私自身の経験でいえば、より抽象度の高い「群論」より、
具体的な代数学や整数論の方が難しくないという感じ。
(でも結局どちらも未だにモノにはしてません。orz)

おそるべき「関数禁止令」は論外として、
以下のような考えがベースにあるので、元記事に納得できます。

・ソフトウェアは、実際に保守する人のスキルでもって
保守可能なものとしなければならない。

・現実、ひとつのプロジェクトにおいて高いスキルを持つ開発者は少ない。
(開放閉鎖原則を意識できるような人は相当のスキルあり)

・ソフトウェアをレイヤ別構成とした場合、高スキルの者が開発するレイヤと
低スキルの者が開発するレイヤでは、コーディング方針が違うこともある。

>「抽象化しすぎる」という状況もよくわかっていないです。

テンプレート使っちゃうとか?
これは人によって相対的に評価されることかもしれません。

minekoaminekoa 2008/11/02 20:12 ▼抽象化について

>> 抽象化する、というのは、「本質を抽出する」
>
>私は、抽象化については、「本質」というものをあまり意識してません。
>観点によって「それまでになかった“新たな”抽象(共通)概念を創造する」という感じです。

「抽象」ってどういう意味で使ってる?という訳ですね。辞書をひいてみました。

----------------------------
【抽象】
事物や表象を、ある性質・共通性・本質に着目し、それを抽(ひ)き出して把握すること。その際、他の不要な性質を排除する作用(=捨象)をも伴うので、抽象と捨象とは同一作用の二側面を形づくる。
----------------------------
大辞林 第二版

----------------------------
多くのものから共通性を抜き出して概念をつくること.
----------------------------
三省堂 WebDictionary
http://www.sanseido.net/User/Dic/Index.aspx?TWords=抽象&st=0&DailyJJ=checkbox

あらら、なるほど、わたしが変です。まいどまいど申し訳ありません。


抽象化とは、物事から一つの側面を取り出しそれ以外を捨てることで、「取り出す側面を誤ってしまった抽象化」も「抽象化」には違いないということなんですね。取り出す側面を誤ってしまえば、それは大層わけわからんちんなコードになるでしょう。

わたしが「抽象化」と言ってきた物は、本当は「正しい抽象化」と言うべきなのだと思います。適切に選ばれた側面を「本質」と表現しています。共通性は本質を発見する足がかりにはなりますが、本質であることを保証しません。これが「共通化と抽象化は別のものだ、という認識を持つのは大事なこと」に当たります。

誤:共通化と抽象化は別のもの
正:共通化と正しい抽象化は別のもの

なぜ抽象化をこんな風に捉えていたかというと、構造化プログラミングにおいて、概念を段階的に積み重ねるように構造化することを「段階的抽象化」といい、この際の抽象化は共通性に立脚していない、一度しか使われていないコードでも、関数に切り出すようなものだからです。

共通化に立脚した抽象だと「抽象化」が「汎化」の類似概念と捉えられがちですが、わたしは抽象化はエッセンスを取り出す行為に思っています。(これまた オレ定義用語ちっくですみません。でも、)プログラムで抽象を行う際、重要なのは後者だからです。



▼「抽象化しすぎる」について

プログラムを抽象化をしたい動機は、ヘッポコな人間の脳の大規模への著しい脆弱さ加減をどうにかするため、見かけのプログラム規模を減らすことがあります。

また、プログラムを抽象した残りカス(捨象したもの)だって、構造化エンティティのなかに保存されています。情報は失われていません。

これらが、プログラムにおける抽象で、覚えておきたい部分。


で、本題ですが、間違った抽象化をする(抽出すべき側面を取りちがえる)ではなく、抽象化しすぎる(正しい抽出であってもやらない方がよい)と言うケースは、やっぱりあまり無いんじゃないかな、と思います。

抽象とは概念の作成のことですので、

>慣れない抽象概念は、具体的なものよりわかりにくいと。

は、抽象的・具象的以前に、単に知らない概念は知らない、というだけのことじゃないかしら。知らない概念は、コード読むなりドキュメント読むなりすれば良いと思います。

ただ、いちいち辞書引きしながら読むのは大変なので「一見さんのときの可読性は抽象化すると落ちる」はそうだと思います。しかし、「一見さんのときの可読性が落ちるから、正しい抽象化であっても しない方がよい」というのは以下の二つのケースくらいしか思いつかないのです。

・抽象化しなくてもコード規模が十分に小さい
・そのドメインに慣れる気がない(継続して保守する気がない)

前者は正しいです(いや、心情的には「間違っていないです」かな。わたしはそれでもヤっちゃいます)。ですが、人間の脳の規模へのヘタレ具合は半端ないので、よっぽどの事態だと思います。実用的なプログラムではあり得ないと言って良いのかしら?(ちと自自信なしです)関数は○行いないにしろ、とかいう ガイドラインはこれに対するものですね。

(前者を満たさない かつ、の)後者は、これを議論にいれたら、抽象化する/しない以前に、そもそも可読性への配慮自体があまりウエイトが高くなくなっちゃうと思います。意味するところは 最長不到関数万歳、ですから。

また、一見さんの読みやすさと抽象化はまったく両立できないわけじゃありません。その概念に適切で解りやすい名前が付いていればいいのです。名前重要、というわけですね。


田辺さんは「関数禁止令は論外として」とおっしゃっていますが、関数禁止令の動機は、あちこちコードをジャンプして読まなくてはいけないプログラムは読みにくいから、関数禁止!なのです。この動機づけは、デスマーチを呼び寄せる悪の種子です。

わたしは、プログラム規模が十分に小さくはない(例えば、エディタの一画面に収まらない程度には大きい)シーンで行われた正しい(=抽出すべき側面の選択をミスっていない)抽象化に対し、抽象で作られた概念の辞書引きの面倒さを理由にダメ出しすることは、全てのケースで間違っていると思っています。



もう一つ、抽象化しすぎの例として田辺さんが挙げておられる

>テンプレート使っちゃうとか?

ですが、これは単に言語機能、または抽象に用いる道具についての知識が足りないだけですので、「抽象化しすぎる」とは関係のないはなし――みんなが使える道具を使おう――と言うだけの話に思えます。

vtwinautomatonvtwinautomaton 2008/11/02 22:18 移植性のためにOS側のインタフェースに合わせて作ったつもりでしたが…申し訳ない。

minekoaminekoa 2008/11/03 01:04 >vtwinautomatonさん
うにゃ、誰にも予想ができないことでしたので。なんか責めるようになってしまって本当に申し訳ないです。


>田辺さん
いろいろ書いてたら、いろいろ思うところが出来てきたので、新しいエントリを作ってしまいました。

田辺さんの質問はいつも鋭いので、自分の中のバグがどんどん明確になっていくようで、とてもありがたいです。

「抽象化しすぎる」については、先ほどのコメントでああいう風に書いてみたものの、なんだかやっぱり私が 酷く勘違いしたこと言っているような気がしてきました。むむむ。

田辺田辺 2008/11/08 03:18 返信遅れました。そう言って頂けると嬉しく思います。
単なる教えて君にならずに済みます。ありがとうございます。