tag:blogger.com,1999:blog-7627811537448503542024-02-20T17:19:18.195+09:00イクスプラズム総合サイトBLOG 映像・写真・情報通信技術T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.comBlogger51125tag:blogger.com,1999:blog-762781153744850354.post-18437480571598361282018-08-09T22:56:00.000+09:002020-03-26T11:06:32.196+09:00量子論と霊の世界<h2>
「霊の世界を信じますか?」</h2>
<br />
うさんくささが先に立つこの問いは、実際とても曖昧でいろんな意味を含んでいるし、霊感商法といった闇の世界から、宗教や死者への尊厳の問題まで広く及ぶので、簡単に答えなど出せるはずはないだろう。<br />
<br />
理化学系に属する人たちの中には、即座に非科学的であることを理由に否定する人がいる。<br />
科学で解明できないことは全て否定するといった低次元な理由が大半だろうが、中にはあと何年もすれば人間が存在する理由も全て科学的に証明できると豪語する、どこぞの大学の教授のような人物もいる。火の玉をプラズマで再現して見せて、調子に乗ってしまったのだろうか?(テレビのバラエティ番組内での発言を真に受けることもないとは思うが)<br />
<br />
ヒッグス粒子の存在が証明された頃、それをきっかけに素人向けの量子論や量子力学の本を何冊か読んでみた。とても楽しかった。<br />
それまでも、光が波の性質と粒子の性質を併せ持っているだとか、アインシュタインが量子論に反発するも、相対性理論の次に、それを土台にして発展した物理学なのだろう、ということは何となく知っていた。<br />
<br />
素人向けの量子力学の説明では数学を使うことができない。そのため、比喩に頼る傾向があり、そしてそれが必ずしも適切でない場合も多々あるようだ。しかし、数学を使わず、僕のような素人にもその本質を伝えようとする本もあった。<br />
そんな本に助けられて、量子論のおもしろいところだけをかいつまんで楽しませてもらったと思う。<br />
<br />
そんな浅い理解でも、自分なりに考え、気が付くこともある。<br />
<br />
たとえば、有名な「シュレーディンガーの猫」という思考実験があるが、僕はこの思考実験の本質とは別に、観測が観測対象に及ぼす影響について思い当たった。<br />
通常、物理の実験では観測そのものが対象(結果)に影響を及ぼさないといった前提がある。<br />
ニュートン物理学までの実験では、厳密には影響を及ぼしたとしても、それが無視できる程小さいので問題はなかった。<br />
しかし、相手が量子になると、影響の度合いが100%、あるいはそれを超えてしまう。なので、非破壊検査はできないと言っていい。<br />
(見る為に光を当てれば、対象がその光により変化してしまうといったようなこと)<br />
<br />
つまり、観測を行うことそのものが、実はもっと本質的な「何か」なのではないか、という考えに行き着いたのだ。飛んでいる蚊を観測するためには、必ず叩き潰さなければならない世界なのだと。<br />
<br />
これは、観測=対象に変化を加える、ということ。「シュレーディンガーの猫」も、猫の入った箱を開けたときに死んでいる、あるいは生きているの2択なのだが、開けない限りは死んでいるとも生きているとも言えない、という状況なのだ。いや、実際には死んでいる、とか、実際には生きているがその情報が伝わっていないだけだ、というのとは違う。箱を開けた瞬間に決定するという思考実験だ。つまり、箱を開けるまでは、猫は死んだ状態でもあり、生きた状態でもある、ということらしい。<br />
しかし、実はこの話には何の矛盾もなく、箱を開けることによって猫に影響を与える、ということが本質なのではないか、と考える。観測が対象に影響を与える、ということを理論に取り入れるべきではないのか、ということだ。実際、どのような観測方法をとったとしても、素粒子を対象とした場合、それに影響を与えてしまうことは、おそらく数学を使って証明できることなのではないか、と思うからだ。<br />
<br />
非常に観念的で現実味の無い話に思えるかも知れないが、物の最小単位である素粒子はそんな世界なのだ。<br />
<br />
この、観測が対象に与える影響という点については、まだまだ考慮すべき点があると思う。ただ、思考実験のように観測自体が無影響であるという前提自体が無意味ではないのかな、ということが言いたいのだ。<br />
もしかすると物理学者はこんなことは当然すぎて問題にしていない可能性の方が高いとも思うが。<br />
<br />
そして、もっと抽象的で漠然とした考えにも行き着いた。今の量子力学、量子論はまだ完成していない、発展途上であることは誰もが認めるところではあるが、現時点でこの理論は非常に複雑、いや、必要以上に複雑すぎるという点だ。まるで、天動説を元にして天体の動きを説明しているかのようだ、と感じたのだ。あまりに、例外が多すぎる気がする。一本筋の通った、美しい理論とはほど遠い。<br />
天動説では複雑怪奇だった天体の動きは、地動説の発見でとてもシンプルになった。<br />
<br />
量子論は、まだ天動説でしか説明できていないように思えるのだ。ひょっとすると、数ある理論の中には、地動説に限りなく接近しているものもあるのかも知れないが、まだそこには至っていないというのが現実であろう。<br />
<br />
天動説から地動説への革命的変化のようなことが、量子論にも起こらなければ、現在謎とされている事の全てを説明できる日は来ないかも知れない。しかし、ひょっとするとそれは人間には永遠に解けない問題かも知れないが。<br />
<br />
<br />
そんな謎が解明されれば、ひょっとすると宇宙の存在理由も判るのかも知れない。そしてその頃になれば、霊であるとか、魂の存在であるとか、自ずと解明されている可能性もある。<br />
<br />
そんな考えの1つのきっかけは、「量子のもつれ」という現象だ。説明は省くが、この現象では情報が光の速度を超える。距離には関係なく、瞬時に伝わるというものだ。現象は知られていても、理屈は解明されていない。<br />
<br />
たとえば、これは無いとは思うのだが、人間などの脳がこの現象を利用して情報伝達が可能だったとすれば、テレパシーが従来の電磁波などによるものと想定した観測方法で感知できなくても当然なのだ。まぁ、これはいかにも素人が考えつきそうな話ではある。<br />
<br />
しかし、目に見えるものしか信じようとしない現実論者が見ているものは、いったい何なのだろうか、という考えに飛躍した。彼らは量子論を知らないし、知ったとしても宗教のように単なる絵空事のようにしか感じ取ることしかできないのかも知れない。<br />
しかし、この世界の大元は、そのような人間にとって観念的でさえある物理世界によって成り立っているのは、現実なのだ。<br />
<br />
そうした物理的な世界において、宇宙の存在理由でさえまだ何も判っていないのに、増して、人間の存在の、その奇跡の度合いは計り知れないものがあるということなのだ。つまり、我々が見ている現実は、奇跡の中の奇跡なのだ。人間が存在するのだから、霊の存在が現実的だとか、非現実的だといった議論はほぼ無意味に思えるのだ。<br />
何の意思もなく、自然に、勝手に人間が存在することなど、僕には全く考えられない。<br />
人間はそれを神と呼ぶのだろうが、それは量子論をはじめとする物理学の延長にあるものののはずだ。<br />
<br />
その宗教ではない神が人に隠し事をしていて、しかしそれが隠しきれない何か、それが霊的な現象として認知されているのかも知れないと思う。<br />
<br />
<h4>
霊的な体験</h4>
<br />
奇跡と偶然は相反することではない。<br />
<br />
たとえば、ある夜に突然亡くなった人が、その日の昼に現れた虹を見舞客と一緒に見ていた。その虹を見ながら、「親類の葬儀の帰途にも虹が出て、あれを渡っていったのね」、という話を見舞客に伝えていた。その時は元気だった。<br />
しかし、その夜容体が急変して亡くなったのだ。<br />
<br />
この昼間の虹は、単なる偶然だろうか。あるいは、奇跡なのだろうか。<br />
<br />
虹を見た夜に亡くなった人の事を知らなければ、偶然ですらない。知っていたとしても、見舞客なら奇跡だと感慨をもつだろうし、他人の感情に寄り添えない立場ならただの偶然で片付ける。見方の違いだ。<br />
<br />
仮に、昼間の虹を見せたのが霊的な人間以外の意思だったとする。であれば、その「意思」は偶然を装い、数学的に、物理的に矛盾がないようにうまく装って虹を発生させるに違いない。<br />
<br />
だから、偶然と奇跡は見方によって違うだけだと言ってもいい。<br />
光が、粒子の性質と波の性質という、相反する性質を併せ持っているのと少し似ている。<br />
<br />
<br />
実は、上記の虹を見た夜に亡くなったのは僕の母であり、見舞客は僕と母の友人だった。<br />
<br />
亡くなった後も、母の事で親類で集まりがあったとき、天気はずっと雨だったのに、みんなが目的地で車を降りる頃には雨が止み、建物の中にいるときは雨になり、また車に戻る時間になったらまた止んだ。雨はけっこう激しかったから、傘を差さずに済んだのは助かった。<br />
母は医大に検体をしていて、3年を過ぎて火葬になった日も、その前後はずっとずっと雨続きだったのに、その日だけ晴れた。その日ももちろん親類や友人など母の縁者が集まった。もちろん、日程は1ヶ月前で天気予報もまだ出ていない時期に決まっていた。<br />
<br />
これらの天候に関しては、僕には単なる偶然には思えない。<br />
<br />
<br />
夢の話もある。昔、お世話になっていたクラシックギターの先生の夢を、ある日唐突に見た。何のきっかけもなかったし、最後にお会いしてから30年は経っていた。なのでとても気になってしまい、ネットでいろいろ探してみたが情報は得られなかった。1年位して、またその先生の夢を見たので再度ネットで検索すると、最初に夢を見た一週間前くらいに亡くなっていたことが判明した。49日が過ぎるまでは亡くなったことは公表されていなかったらしい。このことは日記を付けていたので、はっきりとしている。<br />
<br />
他にも、親戚が亡くなって、やはり一週間以内くらいにその人の夢を見たこともあった。<br />
<br />
天候などとは違い、夢という曖昧ではあるが、その人個人だけに係わる現象だから、<u>あちらの</u>意思が伝える手段としては簡単なのかも知れない。<br />
<br />
<h4>
宗教</h4>
<br />
偶然は、見る人や立場によって奇跡になる。同じように、人間にとって不可解であったり、科学的に説明できない現象も奇跡だったり、神の御業だとか、祟りだとか解釈される。<br />
宗教の多くは、その解釈を人間の想像力で膨らませたものだと言っていいと思う。信じる人にとって、それは事実と同じなのかも知れない。<br />
<br />
しかし、過剰な解釈は人間の都合や欲に利用される。人々をコントロールしたり、金儲けだったり。<br />
だから、僕は大方の宗教に深く加担しないように気をつけている。<br />
<br />
しかし、一方では、多くの人が何かを一心に信じることで、「何か」に影響を与える可能性を否定できないでいる。本当かどうかは僕は疑わしいと感じているが、量子の原理を使ったランダム数発生装置が、大勢の人の活動によって確率が偏った、という実験があるようだ。この実験の前提となる環境は客観性に乏しく、甚だ疑わしい部分もあると感じてはいるが、事実だとすれば非常に興味深い。<br />
いずれにしても、人の精神的な活動が物理的な影響を生む可能性は否定できないと考えている。それが、宗教活動の一部において、リアリティーを持たせている可能性があるのではないか、と考え、僕は真面目に神社でお参りするようになった。<br />
以前は、神道に対して胡散臭い占いと同レベルでしか向き合っていなかったように思うが。<br />
<br />
<br />
<br />
まだまだ、漠然とした結びつけしかできてはいないが、量子論と霊は、案外近しい存在なのかも知れない、と考えている。<br />
<br />
<br />T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-33633959588723843602018-03-18T21:09:00.000+09:002018-04-05T23:19:26.508+09:00PCが売れなくなった本当の理由<i>「開発環境」カテゴリーを追加し、最初の投稿です。</i><br />
<br />
<h3>
主な問題</h3>
PCが売れなくなったと聞きます。スマホにその役割の多くを奪われた結果と思われます。<br />
しかし、PCそのものにもその一因があるようにも思えるのです。<br />
<br />
今、個人的にWindowsのノートPCが欲しいと思っています。ノートPCは比較的高額で、昔から必要に迫られない限り買う事はありませんでした。普段は主に自作のデスクトップを使用しています。<br />
<br />
それがいいのか悪いのか、あるいは先取りなのか、2in1という新たに発現したPCがあります。Windows 10の機能を最も活かすハードウェア、というか、Windows 10の為に作られたPCと言ってもいいのかも知れません。<br />
<br />
残念ながら、僕自身は2in1PCを直接的に必要とはしていません。しかし、アプリケーションを作成するためには、全ての機能が使える環境を手元に置いて、実際に使い続ける必要があります。つまり、アプリケーションのターゲットとしての意味があるのです。<br />
<br />
Appleは現時点で2in1PCを遠くから静観しているだけのように見えます。また、iOSとmacOSを統合することも実現できていません。<br />
<br />
しかし、AppleとMicrosoftでは前提となる環境に違いがあります。AppleはモバイルOSとしてiOSは大成功を収めていますが、Microsoftは完敗状態でした。完敗したOSを捨てることに躊躇う必要は無かったはずです。そこで、主流であるWindowsにモバイルOSとしての機能をくっつけてしまったわけです。<br />
対して、AppleはPC用もモバイル用も成功していますから、無理にくっつける必要はないのかも知れません。<br />
<br />
Microsoftは世界で一番普及しているOSをモバイル対応とすることでスマートフォンなどの巻き返しを考えたのかも知れません。<br />
しかし、MicrosoftはWindows 8で一度失敗しています。しかも、失敗をよく反省もせず、MicrosoftはWindows 10を世の中に押しつけてきました。特に、デスクトップPCを使っている人にとって何のメリットも無い、むしろ害悪となる非常に視認性の悪いデザインは今でも大変不評です。<br />
<br />
では、2in1PCのように、タッチパネルを使ったPCでWindows 10は本領を発揮するのでしょうか。僕はまだ店で少し触っただけですが、とても水を得た魚というような印象を持つことはできませんでした。もちろん、タブレットモードも試してみました。<br />
最も当てはまる言葉は「どっちつかず」、あるいは「中途半端」です。<br />
<br />
そして、あらためて思うのは、タッチパネルに無縁な環境なのに、それがまるでタッチパネルのためのような見た目ののっぺらぼうで平面的なデザイン。そして、それは決してタッチパネルを通して使ってみても使いやすいわけでは無いという事実です。<br />
Windows XP時代には1つのウインドウにいくつものカラーやサイズを設定できる項目がありました。Windows 7はそれがずいぶんと減りましたが、それでも完成度の高いデザインでほとんど不満はありませんでした。視認性が高く、凝ったデザインなのに湯水のごとく当たり前にストレス無く使うことができました。<br />
それまで培ってきたデザインの集大成、研究の成果が盛り込まれた素晴らしいものだったと感じています。<br />
<br />
しかし、Windows 8、10でMicrosoftはその長年の成果のほとんどを捨て去りました。画面を見ているだけで目が疲れます。特に、ファイルエクスプローラーはほとんど全体的に白っぽく見えるため、非常に大きなストレスを感じてしまいます。<br />
非アクティブのウインドウのタイトルの背景が白に固定されているため、これも非常に鬱陶しく感じます。<br />
<br />
Microsoftは頭がおかしくなったのでしょうか。いえ、頭を失ったのですね。しかも、失った頭自体は寄付という自己満足の偽善道楽に興じていて、胴体を顧みることはもう永遠にないのでしょう。<br />
この企業は相変わらず巨大ですが、過去の信用という遺産を無駄遣いし、破壊しながら生き恥をさらしている衰退途上企業と言っていいのではないでしょうか。<br />
<br />
このMicrosoftの体たらくでPCが以前より売れるようになるはずなど、あり得ないと思います。<br />
スマホにユーザを取られたことを除き、PCが売れなくなった最も大きな原因は、Microsoftの体たらくにあると思うのです。<br />
<br />
<h3>
もう1つの大きな問題</h3>
そして、Microsoftの体たらくに同調するかのように、各社PCメーカーの体たらくもあるのです。<br />
PCなのになぜか不便な方に向かっています。拡張性も放棄したようです。この点ではAppleも同罪ですね。<br />
しかし、ハードウエアメーカーとしてのMicrosoftやApple以外のメーカーの最も共通する具体的な問題は、ノートPCの画面を概ね16:9に揃えてしまったことです。特に、13インチ台の小型PCを16:9にすると、縦のサイズがごく僅かとなってしまい、文章などの編集に非常に大きなストレスを感じてしまうのです。<br />
そもそも、16:9はどこから来たのでしょうか。動画のハイビジョンサイズしか考えられませんが、これはPCのワークエリアとして全く適性がありません。<br />
16:9のパネルは安く生産されるため、弱小のメーカーではこれを仕方なく使って安く上げて利益を確保している、と思っていました。しかし、実際にはわざわざ16:9にしているという情報を得ることがありました。だとしたら、なんと愚かなことでしょうか。<br />
<br />
仕方なく13.3インチの16:9サイズのノートPCをしばらく使っていました。しかし、流石にメインで使うことはできませんでした。ネットサーフィンでさえ我慢を強いられます。<br />
16:9の画面でPCを作るなど、非常に愚かなことだと感じていますが、バカがバカの真似をしてAppleとMicrosoft以外のほとんどのノートPCは16:9なのです。昔のように4:3にしろとは言いいませんが、13インチ台ならせめて3:2、15インチ台なら最悪でも16:10ではないでしょうか。<br />
<br />
こんな不真面目なPCを真面目に使う気になれないのは、至極当然のことなのです。こんなクソPCを生産して売れなくなったと嘆いているバカどもに、本質に立ち返れと言いたいのです。<br />
<br />T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-90645847002814189062018-02-14T14:11:00.002+09:002018-02-16T22:44:42.258+09:00身につけるべきプログラマの習慣 その7<h2>
本当の問題を探る</h2>
<div>
人は最初に入ってきた情報を優先しがちです。先入観もそういったことの1つです。</div>
<div>
だから、何か問題が起こったとき、それを見たまま聞いたまま受け取ってしまいます。</div>
<div>
更に悪いことに、問題の本質にたどり着けないまま我先にと夢中になって解決策を考案してしまうのです。</div>
<div>
<br /></div>
<div>
日常茶飯事の光景ではないでしょうか?</div>
<div>
<br /></div>
<div>
本当の問題は何か、一度咀嚼して考えなければなりません。</div>
<div>
まずは、それが誰にとっての問題であるか、という点が大きなヒントになります。立場を変えれば、それは問題ではないかも知れません。誰かにとって問題でも、それが誰かにとってはメリットかも知れないのです。</div>
<div>
<br /></div>
<div>
そして、問題の本質を探るときに大切なことは、客観的視点です。問題の渦中にいる感覚でいるうちは、本質的な解決策を見出すことはできません。</div>
<div>
<br /></div>
<div>
そして、候補に挙げた解決策が、どの問題をどのように解決するのかを予測します。1つの問題を解決することで、別の新たな問題を発生させてしまうことにも注意を払います。</div>
<div>
そのような副作用の問題を含め、一気に解決できることが理想かも知れません。そのような理想的な解決には、発想力、創造力、想像力が欠かせません。</div>
<div>
<br /></div>
<h3>
創造力が大切</h3>
<div>
矛盾する要素を1つ高い次元で解決して融和させることは、創造的脳活動の本質だと考えています。</div>
脳のこの能力を引き出させるためには、記憶依存の思考を行っていてはいけません。また、スケジュールなどに追われ、尻を叩かれながらの作業でも決して行えません。<br />
<br />
精神的なゆとりや前向きの気持ちをもつことが大切ですが、それにはそういった環境が必須になります。<br />
残念ながら、日本の企業の会社勤めではそういった環境はほとんど望むことはできません。日本人は記憶に頼る教育しか受けていないからかも知れません。<br />
<br />
僕自身は長年在宅で、自分の体調と向き合いながらその能力を自分なりに引き出してきました。そのことで、大技、小技を繰り出してきました。もし、部屋に監視カメラが取り付けてあれば、そんな技を繰り出す前は、まるでサボっているように見えることでしょう。<br />
<br />
発想力を必要としない、コツコツとした非創造的な作業においては、とにかく手を動かしなさい、という製造業的なやり方でも世界に通用していたかも知れませんが、ソフトウエア開発というのは、言わば創造的思考の連続でもあります。少なくとも、そういった作業フェーズの時だけは在宅での作業を認めるなど、創造性を引き出す環境作りに真剣に取り組まなければ、ますます日本の技術力は地に落ちていく一方になると思います。<br />
<br />
<br />T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-22632852942585717362018-02-13T18:32:00.000+09:002018-02-14T19:42:05.040+09:00身につけるべきプログラマの習慣 その6<h2>
試験でのバグ出しは誉れ</h2>
<div>
バグ出しでたくさん発見されると成績を下げるという評価があります。これは最低です。</div>
<div>
また、バグ出しなのに、自分のコードにバグが見つかるといちいちがっかりして落ち込む人を見かけますが、それもおかしいですね。</div>
<div>
<br /></div>
<div>
バグというのは、人間なのだから必ず入り込むものです。その率は人によって異なりますが、多かれ少なかれ必ず入り込むのです。</div>
<div>
<br /></div>
<div>
最終的にバグを取り去れば良くて、途中でのバグの件数自体は問題ではありません。</div>
<div>
<br /></div>
<div>
試験の局面においては極力バグを洗い出すことが重要で、次の行程に問題を残さないようにしなければならなりません。</div>
<div>
だから、既にできたコードに対しては、極力バグを残さないようにできる限り多く発見することが誉れであって、恥ではないのです。</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
もちろん、バグにも質があります。単純なミスが原因なものもありますし、ロジックをよく理解しないまま当てずっぽうに書いたコードが動かない、という最低レベルのものまであります。</div>
<div>
後者の場合、プログラマの資質にまで問題の範囲が及ぶことになります。動くはず、という確証のないままコーディング行程を終了させたのは深刻な問題かも知れませんね。</div>
<div>
また、単純なミスであっても、あまり多発するようではデバッグを含め、余分に工数を要してしまうことにはなります。</div>
<div>
<br /></div>
<div>
<div>
プログラミング時にいかにバグを入れないようにするか、それもソフトウエア工学の1つです。そういったことをちゃんと守った上で発生するバグは、通常さほど深刻なものにはならないはずです。</div>
<div>
<br /></div>
<div>
もし、バグを恥じるのであれば、納品後に発生すること、あるいは、バグの量ではなく、悪質なバグに対してなのではないでしょうか。</div>
</div>
<div>
<br /></div>
<h3>
笑い話?</h3>
<div>
30年位前の、大手企業での実話です。</div>
<div>
バグの発生率は統計的に決まっているという点に着目し、ある人の出したバグがその範囲に収まっていない場合、試験を通過させないというものでした。</div>
<div>
これはバグが多発した場合もそうですが、バグが少なすぎても試験をパスさせないのです。</div>
<div>
そこで、あるグループのリーダーは、バグが少なすぎるプログラマに対して、わざとバグを入れ込めという命令を下しました。</div>
<div>
<br /></div>
<div>
プログラミングの本質を理解していない、あるいは、その内容を理解しないまま評価しようとすると、こういう馬鹿げたことになるのでしょうね。無能な管理者が前提の方法論なのです。</div>
<div>
<br /></div>
T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-81414257037013954072018-02-11T23:01:00.003+09:002018-02-15T09:21:51.134+09:00身につけるべきプログラマの習慣 その5<h2>
表と裏を区別する</h2>
<div>
もしもこれが人間性の話だとしたら、もちろん裏表はない方がいいですよね?</div>
<div>
でも、プログラミングにおいては、いつも表と裏をはっきりと区別しなければなりません。外面と、内面を意識すると言い換えてもいいです。</div>
<div>
<br /></div>
<div>
外面、それはインタフェースの部分に相当します。</div>
<div>
アプリケーションの外面ならユーザインタフェースであったり、公開API等の仕様であったりしますね。これはしっかりと仕様を決めなければ話にならない部分でもあります。</div>
<div>
<br /></div>
<div>
ライブラリプログラムだった場合はどうでしょうか。</div>
<div>
C言語のような関数だけでできているライブラリもあれば、C++のクラスライブラリのようなものもあります。</div>
<div>
通常、こういったプログラムはインタフェース仕様書を書いてライブラリのユーザに提供する機能や動作、使用方法や手順をしっかりと定義します。</div>
<div>
同じ機能を提供するのであれば、よりシンプルなインタフェースが求められます。</div>
<div>
<br /></div>
<div>
ところが、仲の良い同一プロジェクトチーム内でプログラムを分担するような場合、ついついそれを使う側も作る側も同じチームだから、内々で済ませてしまえるという甘えが出てしまいがちです。</div>
<div>
インタフェース仕様をきっちり決めずに作り始めてしまったり、決めても作る時の身勝手な都合でそれを守らずにどんどん崩してしまうのです。</div>
<div>
その結果、インタフェースの内側と外側の区別がなくなってしまい、ぐだぐだになってしまうのですね。これはプログラムの品質が格段に落ちることを意味しています。とても深刻な事態なのです。</div>
<div>
<br /></div>
<div>
もし、ぐだぐだになってしまっても誰にも見せない内々の話なんだから構わないだろう、と考えたあなたはかなりやばいです。</div>
<div>
<br /></div>
<div>
<div>
ライブラリに限らず、プログラムには使う側と作る側があります(一番外側のプログラムは使う側の立場のみとも言えますが、たとえばmain()関数はOSから使われると考えることもできます)。そこをしっかりと区別しましょう、ということですね。これはむしろ本能的な感覚として身につけておくべき基本的な習慣だと、僕は感じています。</div>
<div>
<br /></div>
<div>
プログラムを関数やクラスといった単位、あるいはもっと小さな単位かも知れませんが、そのまとまりごとに内側と外側を意識して作ることが大切です。</div>
</div>
<div>
<br /></div>
<h3>
オブジェクト指向プログラムの醍醐味</h3>
<div>
オブジェクト指向プログラミングで、クラスのユーザ側から見える仕様を設計することはとても楽しいことです。単なる関数と違って、設計方法を工夫することで非常にスマートでセンスの良いクラスインタフェースを実現できるからです。言わば、外面を格好よくできるのですね。</div>
<div>
<br /></div>
<div>
1人の担当者がそのユーザにもなるし、制作者にもなることは普通のことですが、他の章でも書いたように、明日の自分は他人ですから、その他人に向けてよいプログラムを作る、という意識を持つことができるわけです。</div>
<div>
そして、実際に明日そのプログラムを使ってみたときに感じる快感は、ある意味独りよがりなのかも知れませんが、それもプログラミングにおける醍醐味の1つだと考えています。</div>
<div>
<br /></div>
<div>
<br /></div>
T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-60941089527463277532018-02-10T23:59:00.001+09:002018-02-15T09:42:52.019+09:00身につけるべきプログラマの習慣 その4<h2>
全ての選択には理由がある</h2>
<div>
どんな些細なことでも、選択肢があればそこに優劣があります。<br />
多くの場合、論理的に優劣の説明ができ、普遍的な選択というものが決まってきます。<br />
そのことを無視して、何も考えずに自分独自の選択をするほど、その仕事の結果はチープになっていくのです。<br />
<br />
もちろん、本質的な問題は個々の事情によって異なる場合も意外に多いと言えます。<br />
問題の焦点の当て方によって、解決すべき問題が異なり、同じ選択肢であってもいつも正しい答えが1つとは限らないこともあります。<br />
<br />
普遍的で絶対的な答えがあったとしても、それさえも揺るがすような事情が本当にあるかも知れません。そのことを理解していれば、闇雲に他人の書いたコードを尊大に否定することはできないはずです。<br />
<br />
<br />
しかし、数々の問題を検討した結果ではなく、何も考えずにあなたの個人的な経験だけを全ての世界だと信じ、最初に見たたままの習慣を間違っていてもそのままにしておくことはみっともないばかりか、能力の低いエンジニアであると、密かに、しかし確実に評価されてしまいます。<br />
増して、その理由に自分のセンスだとか、美学だとかを持ち出しようものなら、挽回できないほどに信用は失墜します。<br />
<br />
どちらでもいいということは、特にこのICTの世界ではほぼあり得ません。しかし、それを知った上、理解した上で妥協をした方がいいことも多々ありますので、そこは誤解のないように。<br />
<br />
<h3>
事例</h3>
</div>
<div>
C言語系のプログラミングソース、中括弧「{}」の始まりをどこに書きますか。</div>
<div>
これは、始まりと終りの括弧を同じインデントカラム位置に書くのが正解です。しかし、中括弧の始まりをその上にあるifだとかwhileだとかと同じ行の一番後ろに書く人が多いですね。これは、昔々、大昔、ソースコード印刷時の行数を減らすための習慣があった頃にできたC言語の教科書の書き方に習っている人が多いことが原因だと思われます。<br />
多くの人が正しさよりも最初に見た<b>すりこみ</b>に支配されているのです。</div>
<div>
<br /></div>
<div>
僕自身も、C言語を始めて5年位は、なんとなく疑問に感じながらもそのようなスタイルで書いていました。しかし、ある日ある開発環境でソースコード整形ツールを使ってみると、全て始まりの中括弧はカラム位置を揃えられてしまいました。最初は戸惑いましたが、その書き方の方がつまらないバグやミスを減らせることを実感できたので、以来その描き方を貫いています。(同じ行で中括弧を閉じる場合は別です)</div>
<div>
<br /></div>
<div>
その選択の理由は体験そのものではありません。体験を元にした普遍的な理屈です。</div>
<div>
それは・・・</div>
<div>
<br /></div>
<div class="indent-1">
人は図形を素早く感覚的に認識する能力があります。<br />
中括弧の始まりと終りが同じインデントのカラム位置に存在することで、図形的に囲まれていることを直感的に認識することができるのです。そのことで、コードの視認性が格段に高まります。<br />
一般の文章でも、罫線に囲まれていると直感的にそこがまとまっていることを認識しやすいでしょ?(日本人は特に好きなはず)<br />
それと一緒です。<br />
<br />
逆に、他の某言語のように begin と end という単語で囲まれているとあまり直感的に見ることができなくなり、視認性が悪くなりますね。<br />
同様に、中括弧の位置がずれていれば、結局その分余計な情報処理が脳で起こってしまうので、始まりと終りの位置を揃えたコードに比べてやや視認性に劣ってしまいます。<br />
<br />
せっかくC系の言語は図形的な表現をして視認性を高めるデザインになっているのに、わざわざそのメリットを削ぐことは正しくないと断言できます。</div>
<br />
<div>
ですが、長年そのスタイルで書いてきたプログラマに文句は言いません。スタイルが統一されていれば、そこまで読み辛いということもありませんし。<br />
でも、一番正しい正解というわけでもありません。</div>
<div>
改善する力が残っているならば、よりよい選択をすべきです。<br />
<br />
<br /></div>
T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-6054560173146552052018-02-10T22:04:00.000+09:002018-02-10T23:14:35.506+09:00身につけるべきプログラマの習慣 その3<h2>
明日の自分は他人と思う</h2>
<div>
プログラマであれば、特にコメントに気を遣いましょう。コメントは後で書くのではなく、コードと同時に書かなければいけません。明日どころか、10分後にはなぜそのようなコードを書いたのか判らなくなることさえあるからです。</div>
<div>
<br /></div>
<div>
しかし、プログラミングでコメントに依存したコードを書くのは完全にNGです。コメントの内容は必ずしも信用できないからです。特に、修正を繰り返したコードは、コメントがメンテナンスされていないことが多々あります。</div>
<div>
なので、第一義的にはコメントを読まなくても理解しやすいコードを目指します。</div>
<div>
そして、コメントはコード自体では何をしているか判りにくい箇所を捕捉し、コードの可読性を高めることを目的とします。</div>
<div>
<br /></div>
<div>
このようにすることで、多大な複雑系で疲弊し、記憶することを放棄した脳にも優しくなり、明日他人になった自分を取り戻すことができるようになります。</div>
<div>
<br /></div>
<div>
そして、それは本当の他人に対しても優しくなり、保守性、つまりソフトウエア制作における「質」の要素の1つ、を改善することができるのです。</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
自分を含めた他人に対して優しく作ること、それは善意のあるソフトウエア制作であり、技術者の善意、という最近では忘れられてしまった大切な精神を思い出させる行為でもありますね。</div>
<div>
<br /></div>
<div>
これなくして、技術者は成立しない「何か」の1つなのではないでしょうか。</div>
<div>
<br /></div>
<div>
<br /></div>
T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-57850064554850805162018-02-10T21:44:00.000+09:002018-02-10T23:16:27.622+09:00身につけるべきプログラマの習慣 その2<h2>
継続的にメンテナンスできないドキュメントは書かない</h2>
<div>
もしあなたに作成ドキュメントの選択権があるならば、まず第一に新規作成より修正のことを念頭に無駄を排除すべきです。</div>
<div>
<br /></div>
<div>
大きなプロジェクトほど無駄なドキュメントを作成する傾向が感じられます。最初に作成したはいいが、結局それが何の為に存在しているのか誰にも理由がわからず、そして役に立たない。役に立たない一番の原因は、メンテナンスが行き届いていないため、内容をどこまで信用していいのかさえ判らなくなっているからです。</div>
<div>
<br /></div>
<div>
少なくとも、システムの内容に変更が生じたときは、修正すべきところを全て洗い出すことができなくてはなりません。そして、修正すべきであることを直接ドキュメントに印を付けることが重要です。修正後の内容を書き込むのは後でもいいので、とにかくその内容が違ってしまったことがはっきりと判るようにだけはしておきましょう。その際、修正IDを併記しておくと良いでしょう。</div>
<div>
そのようにしておけば、もしそのままの状態で放置されてしまったとしても、印の付いていないところはまだ信用できることが判るので、ドキュメント全体が死んでしまうことはなくなります。</div>
<div>
<br /></div>
<div>
同じ内容は1箇所だけに書くようにしろ、という教えもありますが、それは無理があります。もちろん、極力重複は排除すべきですが、概要と詳細に分かれていたり、図面と文章に分けて表現する等、どうしても複数箇所に同一内容が記載されてしまいます。</div>
<div>
<br /></div>
<div>
これらのことを踏まえて、確実にメンテナンスできるドキュメントを作成するように心がけましょう。</div>
<div>
<br /></div>
<div>
とは言え、ドキュメントは必ず書かなければなりません。どうせメンテナンスできないのだから、という理由でドキュメントの作成を放棄するのは大間違いです。</div>
<div>
それは、ひょっとするとコンパイルしてバイナリが生成できたら、ソースファイルを廃棄するのと同じかも知れませんよ。</div>
<div>
<br /></div>
<div>
<br /></div>
T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-67484040471590469082018-02-09T10:26:00.000+09:002018-02-09T10:34:05.904+09:00身につけるべきプログラマの習慣 その1<h2>
上流工程の結果を鵜呑みにしない</h2>
<div>
特に、ウォーターホール開発で上流から流れてきた結果を盲信すると痛い目に遭うことが(よく)あります。少なくとも、1工程分は遡って、どうしてその結果が導き出されたのかを理解し、検証する必要があります。</div>
<div>
<br /></div>
<div>
怠って一番痛い目に遭うのは自分自身です。</div>
<div>
<br /></div>
<div>
問題の有無さえ考えずに上流工程の結果のまま作業をして、最後にそれが動かないことが判明しても自分には無関係のふりをして自己保身をする人を<b>よく</b>見かけます。</div>
<div>
<br /></div>
<div>
<div>
そんな人はいったい何の為に仕事をしているのでしょうか? お金のためだけならもっと儲かる仕事は他に沢山あると思います。</div>
</div>
<div>
良いものを作って誰かの役に立たないなら、その仕事に意味や価値はありませんし、そんなエンジニアは害悪になるだけ、必要とされなくなります。</div>
<div>
<br /></div>
<h3>
体験談</h3>
<div>
割と最近のことです。</div>
<div>
<br /></div>
<div>
某大手ソフトウエア会社でお手伝いさせていただいたとき、詳細設計以後を担うグループに入りました。既存のシステムや改造点について学んでいく段階で致命的な問題を発見しました。工程の初期段階での発見だったために大事には至らず、設計の一部修正を行って頂くことで回避することができました。</div>
<div>
問題はそれを指摘するだけではなく、最善と思われる解決策も併せて上流工程担当に伝えました。このとき、自分はまだ全てを把握&理解しているわけではなかったので、想定される条件によっていくつかの案を提案していました。</div>
<div>
<br /></div>
<div>
僕自身はこれまでにほぼ全行程の経験をしています。しかし、そのこととは関係なく、ICTエンジニアであればそれがどのように具体的に動作をするのか、理解、確認しながら作業を進めることが大切なのだと思います。</div>
<div>
<br /></div>
<div>
言われたとおりにコーディングだけをするプログラマ(コーダ?)は必要とされません。</div>
<div>
<br /></div>
<div>
<br /></div>
T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-48301898221043732452018-01-31T16:30:00.000+09:002019-02-01T11:31:26.760+09:00Cプログラマのための、C++オブジェクト指向簡単裏口入門(11)<h2>
クラス設計お悩みポイント?</h2>
<div>
今日はC++のコーディングから少し離れて、コーヒーブレイクのような記事にします。手を休めて、気軽に考えて見てください。<br />
<h3>
簡単に試すことが難しい?</h3>
僕がオブジェクト指向プログラミングを始めてみてそれが短所だと感じることがあったのは、コーディングをして、その動作確認が出来るようになるまでの時間が長くなったことでした。ちょっとだけ辛抱が必要なのです。<br />
<br />
原因の1つは、プログラムの依存サイクルの違いです。<br />
<br />
C言語では、プログラミングの屋台骨は関数しかありません。関数が、他の関数を呼び、またその関数が他の関数を呼ぶ。<br />
<br />
呼ぶ方も呼ばれる方も自分で作る場合、意味的により要約されているのは呼ぶ側であることが一般的です。とりあえず、要約されている部分を実行したいとなった場合、そこから呼ばれる関数をダミーで作っておくことはさほど難しくありません。実行時間やそのタイミング等に依存しないのであれば、戻り値だけを気にすればいいからです。<br />
<br />
なので、とりあえず通して動かしてみる、ということが比較的簡単にでき、徐々に詳細部分を作り込んでいく、ということが可能です。<br />
<br />
オブジェクト指向プログラミングで中心となるのはクラスです。<br />
通常、クラスは他のクラスのユーザになります。関数が他の関数のユーザになるのと同じです。<br />
<br />
クラスを使う、というのはどういうことでしょうか。<br />
関数を使うというのは、引数があって、その結果としての戻り値しかありません。<br />
クラスを使うのは、多くはまずインスタンス化をして、そして、そのクラス独自の使用方法があり、最後に廃棄する。<br />
問題はその独自の使用方法です。ファイルを扱うなら、オープンがあってクローズをしなければならないかも知れません。クラスのインスタンス化の段階から例外をキャッチしてエラー処理を行う必要もあるかも知れません。<br />
クラスを使うというのは、いくつかの手続きが必要なのです。<br />
<br />
クラスというのは機能を使うための手続きの集約でもあり、つまりインタフェースそのものと言えます。クラスというインタフェースと、関数という手続きがセットになっているので、依存のサイクルは関数だけの場合と比較すると以下のようになります。<br />
<br />
矢印を依存方向だとすると、以下のような依存サイクルになります。<br />
<br />
<b>C言語の場合:</b><br />
<div class="indent-1">
<span style="color: blue;">[関数]</span> → <span style="color: blue;">[関数]</span> → ・・・</div>
<br />
<b>C++のオブジェクト指向プログラミングの場合:</b><br />
<div class="indent-1">
<span style="color: #cc0000;">[クラスインタフェース]</span>+<span style="color: blue;">[関数]</span> → <span style="color: #cc0000;">[クラスインタフェース]</span>+<span style="color: blue;">[関数]</span> → ・・・</div>
<br />
つまり、関数が関数だけに依存しているC言語の場合は、使用する関数のダミーを作るだけで仮の実行が可能です。しかも、それは比較的少数のダミー関数を作ればいいだけになります。<br />
<br />
クラスに依存している場合、依存されるクラスのダミーを作る必要があるのですが、その中で使用するメソッド(メンバー関数)のダミーを全て(通常は複数)作っておいたり、プロパティ(メンバ変数)に外部から直接アクセスさせるのであれば、そこにダミーのデータを設定するなどの仮の処理が必要になるかも知れません。<br />
これは本当に仮なのでしょうか。仮であっても、やや本番に近いプログラミングをする必要があると言えるのではないでしょうか。<br />
<br />
以上のように、ダミーのクラスを作ることが二度手間になる部分もあります。<br />
その無駄を避けるために、クラス中心のプログラミングではトップダウンよりもボトムアップで、依存される側のクラスから実装を完了させていく手順になることが自然な流れになります。<br />
つまり、全体的な動きを試してみる、というのがどうしても後半に回されてしまいがちなのですね。<br />
<br />
このことは、最近注目され、導入事例も増えてきているアジャイル開発とは矛盾してしまうような気もするのです。残念ながら、僕にはアジャイル開発のスキルが不足しています。こういったオブジェクト指向開発と、アジャイル開発をどのように折り合いを付けていくのか、ある程度の想像はできますが、現実を知りません。<br />
<br />
<h3>
プロパティ or 継承の問題</h3>
最右翼のオブジェクト指向であれば、あまり迷うことはなく、全て継承して解決するのかも知れませんが、現実的な解を求められるC++では悩むことがあります。<br />
<br />
たとえば、[人]クラスを作りますが、男女を区別する必要があるとします。<br />
[人]クラスの中にプロパティ(変数)を追加して、そこに男性か女性かを設定する、というのが1つの方法です。<br />
男性は男子トイレを使う、女性は女性トイレを使う、という判定がクラスのメンバ関数の中で必要になったとき、このプロパティを見て判断する訳ですね。<br />
<br />
もう1つの解は[人]クラスを継承して[男性]クラス、[女性]クラスを作ることです。継承したこれらのクラスは、自分の性別を最初から知っているので、どのトイレを使うかは最初から決まっています。なので、判定処理が不要になるメリットがあります。<br />
<br />
最近では、戸籍上の性別が本人の本質的な性と一致しないケースも無視できなくなっています。だから、戸籍上の男性が、女性用のトイレを使うことだってあるわけです。<br />
<br />
プロパティで対応していたクラスであれば、戸籍上の性別だけではなく、本人が実際にはどちらの性で生きているかを示すプロパティを更に追加することになりそうです。<br />
<br />
継承の場合はどうでしょうか。<br />
[男性]クラスと[女性]クラスを更に継承し、両方の特徴を兼ね備えた[男女]クラスを作った上で、更に戸籍上の性と実生活上の性の情報を持って処理を分岐させなければならなくなりそうです。<br />
<br />
トイレに行くという処理は架空の話ではなくて、たとえばある会社での社員が、どちらのトイレを使うのか統計を取り、トイレの設計を行うといった場合に応用する話です。<br />
他にも、更衣室、場合によっては銭湯のようなお風呂、制服、きっと、それぞれによってどちらを使うのかが異なる人もいるだろう、ということを考えると、一概に決めることはできなくなります。<br />
<br />
もし、何かを利用する場合にどちらの性別用のものを選ぶか、という視点で継承を考えた場合、それぞれの性別、たとえばトイレ用の性別クラス、更衣室用の性別クラス、制服用の性別クラス、そんな基底クラスから全ての性質を多重に受け継いだクラスが必要になりそうです。<br />
こうなってくると、やはり人間にとって性別とは単なるプロパティであって、しかも、それは1種類ではないと考えた方が現実にもマッチして自然に思えてきますね。<br />
<br />
継承で表現するクラスは、もう少し普遍性のあるものの方が適しているかも知れません。<br />
仮に継承を使ったとしても、判定が必要になるため、結局プロパティを持つことにもなります。<br />
<br />
じゃあ、何でもかんでも継承はできるだけ避けて、プロパティで分岐させればいいじゃないか、というのもまた違うのです。それを追求すると、クラスはアプリケーションにたった1つで良くなってしまう可能性すら出てきてしまいます。<br />
それは明らかな間違いである、と判りますね(例示は割愛します)。<br />
<br />
<h3>
考えること、議論することが大切</h3>
ああでもない、こうでもないと考えてきましたが、継承とするのか、プロパティで区別するのか、迷うケースは意外にあるものです。しかし、多くの場合最初に思いついた方、あるいは、考えたくないのでプロパティだらけになってオブジェクト指向的ではない設計になってしまうことも多々あります。<br />
<br />
どうか、これを考えることから逃げないで、より合理性の高い解を見つける労を惜しまないようにお願いしたいと思うのです。それは、頭でっかちな理想論を追求することではなく、現実的な解を求めるという行為です。<br />
できれば、いろいろな考え方に気づくため、人と議論をすることをお勧めします。この議論こそが、オブジェクト指向のメリットを活かす力のために必要と考えていますし、醍醐味の1つでもあると感じるのです。<br />
<br />
オブジェクト指向を「やっている」開発現場では、この議論が活発に行われているはずです。多くの解から、何故それを選ぶのか。いつも、他の可能性があることを知りながら、最初に思いついたものを解としているようでは前に進むこと(上達すること)はありません。<br />
<br /></div>
T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-52223748666484566202018-01-28T21:56:00.000+09:002019-02-01T11:31:06.957+09:00Cプログラマのための、C++オブジェクト指向簡単裏口入門(10)<h2>
C++のクラスは魔法の箱?(2)</h2>
<div>
前回、オブジェクト指向プログラミングの醍醐味の1つは、自分が提供するクラスに高い機能をスマートで格好よく組み込めることであると書きました。<br />
しかし、それは演算子をクラスに組み込むことだけではありません。<br />
<br />
ギミックと言える要素は他にもあります。<br />
<h3>
初期化と後片付け</h3>
クラスの設計では、それが1つの独立したアプリケーションの様に動作させることも多々あります。当然、クラス内部の初期化や終了処理を行うことも多いわけです。<br />
<br />
クラスのユーザ側にそれを意識させて、使い始めるときには必ず init() を、使い終わったら term() を呼んでくださいね、さもないとエラーやメモリリークが発生してしまいますよ、と仕様書に注意書きを記すのも1つの方法です。<br />
<br />
しかし、初期化と終了処理はもはや普遍的な定型処理と言えるものですから、C++ではこれを自動化できるようになっています。<br />
<br />
では、それを組み込んだクラスの例を見てください。<br />
<br />
<h4>
コード1</h4>
<pre class="code-sample">// 巨大データクラス
class LargeData
{
size_t m_size;
char * m_pData;
public:
// ★コンストラクタ
LargeData(size_t size)
{
m_size = size;
m_pData = (char *)malloc(size); // エラー処理は省略
}
// ★デストラクタ
~LargeData()
{
free(m_pData);
}
// データの設定
const char * setData(const unsigned char * pData, size_t size)
{
return (const char *)memcpy(m_pData, pData,
m_size < size ? m_size : size);
}
// データの取得
const char * getData()
{
return m_pData;
}
}
// クラスを使う
int main()
{
char myData = "abcdefg";
LargeData ld(1000); // 内部で1000バイト取得
ld.setData(myData);
return 0;
}
</pre>
この LargeData クラスには目新しい関数が2種類追加されています。クラス名と同じ名前の関数と、それにチルダ(~)が頭に付いたものです。それぞれ、コンストラクタ、デストラクタと言われるメンバ関数で、クラスがインスタンス化されるとき、および、破棄されるときに自動で呼ばれます。<br />
<br />
ここでは main() 関数でクラスを使用しています。自動変数として LargeData をインスタンス化する際にパラメータを渡しています。これがコンストラクタ関数のパラメータの引数になります。dl(1000) という書式は、関数を読んで居ることを強調するものですが、この場合引数が1つなので、dl = 1000 という書き方も可能です。(初期化の書式は他にもありますが、ここでは割愛します)<br />
デストラクタの方はいつ呼ばれるのでしょうか。ld は自動変数ですから、main() 関数を抜ける際に破棄されます。破棄されるタイミングでデストラクタが呼ばれます。<br />
<br />
このような作りにしておけば、特に後処理を忘れてメモリリークを起させてしまう心配がとても少なくなるわけです。便利ですね!<br />
<br />
ところで、このコードではメモリの取得にCの標準関数でもある malloc() を使用しています。実は、C++では malloc() を使うことは滅多にありません。なぜなら、代りとなる言語組込みの演算子 new が用意されているからです。free() に相当する delete もあります。 早速書き換えてみましょう。 <br />
<br />
<h4>
コード2</h4>
<pre class="code-sample">// 巨大データクラス
class LargeData
{
size_t m_size;
unsigned char * m_pData;
public:
// コンストラクタ
LargeData(size_t size)
{
m_size = size;
m_pData = new char [size]; // ★
}
// デストラクタ
~LargeData()
{
delete m_pData; // ★
}
// データの設定
const unsigned char * setData(const unsigned char * pData)
{
return (const char *)memcpy(m_pData, pData, m_size);
}
// データの取得
const unsigned char * getData()
{
return m_pData;
}
}
</pre>
new は確保したメモリのポインタを返します。また、delete には開放するメモリのポインタを渡します。<br />
malloc() や free() の代わりに使うような説明をしましたが、互換性はありませんので、new で取得して free() で開放する、といった使い方はできません。<br />
また、malloc() はメモリが確保できない場合 NULL を返しますが、通常 new はメモリが確保できない場合は例外を発生します。(処理系の設定によって、NULLを返すようにもできる場合があります)ので、直後にいちいちNULLチェックをする必要はありません。(最新のC++ではNULLではなく、nullptr キーワードを使用します)<br />
必要なところで例外をキャッチしてエラー処理を行うようにします。<br />
<br />
更に、new と delete は malloc() や free() 関数とは本質的に異なることがあります。<br />
次のコードを見てください。<br />
<br />
<h4>
コード3</h4>
<pre class="code-sample">// クラスを使う
int main()
{
char myData = "abcdefg";
LargeData * pLD = new LargeData(2000);
pLD->setData(myData);
delete pLD;
return 0;
}
</pre>
これは先ほどの LargeData クラスを使用するところです。今度は自動変数としてインスタンス化しているのではありません。new 演算子を使って LargeData クラスをインスタンス化しているのです。<br />
<br />
malloc() は単にメモリを確保するだけでしたが、new は指定したクラスのメモリ領域を確保すると共に、インスタンス化まで行います(厳密には、インスタンス化という言い方は必要なメモリ領域の確保までを含めます)。もちろんコンストラクタも呼ばれます。<br />
また、同様に delete 演算子はメモリを解放するだけではなく、後処理も行い、デストラクタも呼ばれます。<br />
<br />
new や delete はC++では頻繁に使用されます。その際気をつけなければならないのは、delete を忘れてしまうことです。<br />
Java や C# ではその必要が無く、言語のランタイム処理によって自動的に不要になったオブジェクトが判断され、開放される仕組みになっています。C++ ではプログラマの責任で開放しなければなりません。<br />
しかし、最新の C++ では、そういった新しい言語の仕様の影響を受けたためか、ある程度の自動化が考慮されるようになっています。標準のライブラリを使うことで実現します。<br />
<h3>
アイディア</h3>
さて、コンストラクタやデストラクタを使って何をすればいいのでしょうか。単に、クラス内部で使用する領域の確保をしたり、開放したり、あるいはメンバ変数の初期化だけでしょうか。<br />
もし、あなたがファイルをリードライトするクラスを設計するとしたら、どうしますか?<br />
<br />
コンストラクタでファイルをオープンし、デストラクタでクローズする、といったアイディアが思いつきましたか?<br />
しかし、コンストラクタでファイルをオープンしたら、戻り値が使えないのでエラーになったときどうすればいいのでしょうか。そんな心配をされたなら、むしろ良く理解されているということでもありますね。こういうときのためにも、C++では例外処理機構が用意されています。コンストラクタでのエラーは例外を発生させればいいのです。<br />
<br />
コンストラクタで必ずファイルをオープンしなければならないとしたら、場合によっては都合が悪いこともあるでしょう。クラスをインスタンス化だけしておき、あとでオープンすることもできるように、fileOpen() メンバ関数も追加します。また、デストラクタが呼ばれる前にクローズしたい場合も考えて fileClose() も追加します。<br />
<br />
なんだかとても便利そうなファイルIOクラスができそうな気がしてきませんか?<br />
<br />
更に、テキストファイル用に特化されたクラスを作るなら、元のファイルクラスを継承させて設計すればきっと理に適っているはずです。更に発展させて、UNIX系の考え方にならって、コンソールIOとか、ネットワークのIOもファイルとして扱うなら、どのような継承関係にすればいいのか、ワクワクしてきませんか。<br />
<br />
<br />
ここまでにご紹介してきた演算子の再定義や、コンストラクタ、デストラクタ、そして継承。センスの善し悪しはこれらの使い方だけが問題ではありません。実は、それは本質ではありません。<br />
これらを駆使したとしても、クラスのユーザには更に複雑な使用方法をお願いしなければならないことも現実には沢山あります。それを、必要以上に複雑にせず、あるいはブラックボックスにしすぎず、様々なバランスを取りながら設計することが大切です。<br />
クラスを作ることは、1つのアプリケーションを作ることに近い作業で、単に関数を作ることよりもずっとやりがいのある作業なのです。<br />
<br /></div>
T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-52161108634352480052018-01-27T10:20:00.000+09:002019-02-01T11:30:46.451+09:00Cプログラマのための、C++オブジェクト指向簡単裏口入門(9)<h2>
C++のクラスは魔法の箱?(1)</h2>
<div>
オブジェクト指向プログラミングの醍醐味の1つは、自分が提供するクラスに高い機能をスマートで格好いいインタフェース仕様で組み込めることだと思います。<br />
優れたものは適切にギミックを組込み、あたかもマジックボックスのようでもあります。プログラマのセンスや腕の見せ所、重要なアピールポイントになります。<br />
<h3>
演算子関数</h3>
C++標準ライブラリでさえそうなのです。たとえばこれまでの記事で扱ってきた vector クラスは、まるで言語組込みの機能のように、添え字([])を使って配列の要素にアクセスできる、というインタフェースを提供しています。<br />
更に、map クラスに至っては、添え字の中に文字列等を指定できる連想配列を実現しています。<br />
<br />
もし、クラスが提供するファンクションが全て普通の関数タイプだけだとしたら味気ありません。演算子を再定義できればコードの視認性を格段に高められますし、使う側に楽しさを提供することさえ可能なのです。<br />
<br />
これは、一見オブジェクト指向そのものとは無関係に思われるかも知れません。<br />
しかし、実は間接的に関係があるのです。<br />
組込みの型、たとえば int であるとか char であるとか、これらもC++では型であると同時にクラスでもある、と考えるのです。型=クラスと言ってもいいのです。<br />
つまり、クラスを作ることはユーザ定義型を作ることでもあるのです。であるならば、組込み型と同様にユーザ定義型にも演算子が使えないと、それを使用する箇所での見た目が全く違うものになってしまい、ユーザ定義型と言うには少々無理がある、ということになってしまうのです。(ただし、一般的にはクラスを完全な組込み『型』のように綺麗に設計できるケースは少ないと言えます。)<br />
<br />
演算子を使う場合と関数をコールする場合の違いを標準ライブラリの string クラスの場合で見てみましょう。<br />
<br />
<h4>
コード1</h4>
<pre class="code-sample">void func1()
{
string a = "abcd";
string b = "1234";
a += b; // "abcd1234"
}
void func2()
{
string a = "abcd";
string b = "1234";
a.append(b); // "abcd1234"
}
</pre>
func1()では演算子を使っているので、文字列の接続を直感的に理解できるコードになっています。func2()は、+=と同じ動作をする append() メンバ関数を使っていますが、append という単語の意味を知っていたとしても、やはり演算子の視認性に比べれば劣ります。ぱっと見が大切なのです。<br />
<br />
なお、この append() は実際に存在している関数です。なぜ、+= 演算子が使えるのに、と思われるかも知れませんが、使用する側の都合で演算子よりも関数を使う方が適切と判断される場合があるからかも知れません。<br />
たとえば、append() には引数を複数指定する機能の詳細が異なるバージョンが多重に定義されています。そういった他の append() と並列に使用する場合、1カ所だけ += 演算子を使うことはせず、append() で揃えたい、といった場合が考えられます。<br />
<br />
この記事ではC++の文法自体はあまり説明してきませんでしたが、演算子を再定義する方法はここでご紹介しておきます。演算子を関数を使って再定義するには operator というキーワードを使います。<br />
+= 演算子を再定義する例を示します。<br />
<br />
<h4>
コード2</h4>
<pre class="code-sample">// 座標クラス
class Point
{
int m_x;
int m_y;
public:
// + 演算子の定義
Point & operator += (const Point & rPoint)
{
m_x += rPoint.m_x;
m_y += rPoint.m_y;
return * this; // 自分自身を返す
}
}
int main()
{
Point a, b;
a += b;
return 0;
}
</pre>
このように、operator というキーワードの次に再定義する演算子を記述し、合わせて関数名のようになります(ただし、呼出す方は関数呼出の書式は使えません)。再定義できる演算子は限られていて、全てを書き換えられるわけではありません。演算子の優先順位を変えることもできません。<br />
それと、再定義と言っていますが、コード2の場合、Point クラス同士の演算の場合に限られます。それ以外の組み合わせには影響しません。<br />
<br />
また、演算子は + なのに、中身では引き算をする等、使用者を混乱させるような定義は避けなければなりません。これは、故意に行わなくても、特に複雑な処理になる場合など、結果的に演算子の意味をこじつけてしまい、直感的に理解しにくい動作を定義してしまうことはありがちです。そのような場合は無理をせず、通常の関数として実装した方が好ましいかも知れません。この辺りのさじ加減はプログラマのセンスが問われるところでもあります。<br />
<br /></div>
T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-79236805514309658952018-01-19T18:15:00.001+09:002019-02-01T11:30:28.651+09:00Cプログラマのための、C++オブジェクト指向簡単裏口入門(8)<h2>
オブジェクトはどのように対象を写像するのか?(2)</h2>
<div>
まずは、前回の最終コードを見てみましょう。<br />
<br />
<h4>
コード1</h4>
<pre class="code-sample">// 名刺クラス
class NameCard
{
string m_name; // 氏名
string m_mobTel; // 携帯番号
string m_email; // メアド
string m_address; // 住所(通常は自宅)
string m_title; // 肩書き
string m_date; // 名刺交換日
string m_memo; // メモ
vector<Company *> m_pCompanys; // 所属会社 0~n
};
// 会社クラス
class Company
{
string m_name; // 会社名
string m_department; // 部署
string m_address; // 住所
string m_tel; // 電話番号
vector<NameCard *> m_pNameCards; // 社員 0~n
};
</pre>
このコードを見ながら、印刷機能について考えていきます。<br />
<br />
印刷処理を1から作るのは大変なので、何かのライブラリを使うことにします。<br />
クラスに print() 関数を追加しますが、中身は主に自分のクラスにある情報を集め、ライブラリに渡して印刷を依頼するだけと考えてください。コード自体は示しません。<br />
<br />
また、印刷関数自体は外部から呼ばれますが、印刷するかしないか、というのはクラスの内部にフラグを持たせることにします。<br />
では、これらを追加してみましょう。<br />
<br />
<h4>
コード2</h4>
<pre class="code-sample">// 名刺クラス
class NameCard
{
string m_name; // 氏名
string m_mobTel; // 携帯番号
string m_email; // メアド
string m_address; // 住所(通常は自宅)
string m_title; // 肩書き
string m_date; // 名刺交換日
string m_memo; // メモ
vector<Company *> m_pCompanys; // 所属会社 0~n
bool m_isPrint; // ★印刷フラグ
public:
// ★印刷フラグのセット/リセット(パラメータを指定しない場合はtrue)
void setPrintFlag(bool isPrint = true){ m_isPrint = isPrint; }
// ★印刷フラグの取得
bool isPrint(){ return m_isPrint; }
// ★印刷処理
void print();
};
// 会社クラス
class Company
{
string m_name; // 会社名
string m_department; // 部署
string m_address; // 住所
string m_tel; // 電話番号
vector<NameCard *> m_pNameCards; // 社員 0~n
bool m_isPrint; // ★印刷フラグ
public:
// ★印刷フラグのセット/リセット(パラメータを指定しない場合はtrue)
void setPrintFlag(bool isPrint = true){ m_isPrint = isPrint; }
// ★印刷フラグの取得
bool isPrint(){ return m_isPrint; }
// ★印刷処理
void print();
};
</pre>
まず、印刷フラグなのですが、わざわざフラグの実体(bool m_isPrint)を非公開にして、公開した関数で間接的にアクセスするようにしています。最初から m_isPrint を公開しておけばいいのでは? と思われるかも知れません。しかし、これがオブジェクト指向の流儀なのです、ではおそらく納得できないでしょう。実効速度が若干おちる可能性も無いわけではありません。<br />
<br />
しかし、こうしておくことで実際にフラグをセット、あるいはリセットする際に、別な処理をあとで追加する事が容易くなります。今回の場合はそういった処理が追加になる可能性がある、と考えてこうしています。(他にも、デバッグ用のコードを埋め込む際にもこの方が都合がいいので、直接メンバ変数を公開するのはむしろ例外的かも知れません)<br />
<br />
ところで、setPrintFlag() 関数はクラスの定義の中に実装まで行っています。通常はクラスのメンバ関数はプロトタイプだけを宣言し、実装は別の箇所(別のソースファイル)で行います。このように実装コードをクラス定義中に記述すると、関数はインライン展開されるはずで、関数呼び出しのオーバヘッドがキャンセルされることになります(ただし、その場合同じコードが複数展開されることになります)。<br />
また、C言語にはない、引数のデフォルト値を使用しています。呼出で引数を省略するとデフォルト値が設定されます。<br />
<br />
<br />
名刺、会社のそれぞれのクラスに print() 関数を追加しました。<br />
<br />
名刺のオブジェクトや会社のオブジェクト自身に印刷させるというのは、悪い考え方ではありません。では、それを何処で呼出すのでしょうか。この print() 関数を呼ぶと、すぐに印刷処理が実行されてしまいます。<br />
複数の宛先を一気に印刷する、という機能も必要です。<br />
<br />
機能としては、ユーザの操作で一覧から印刷対象のデータにチェックをして、最後に印刷開始を指示する、という手順になります。<br />
プログラムとしては各クラスの印刷フラグを立てて(setPrintFlag()の呼出)、次にチェックされているものだけを実際に印刷する(print()の呼出)、ということになりそうです。<br />
<br />
しかし、print()を呼出す際、印刷の対象とするクラスが2種類あるため、わざわざ2つのクラスに対応した印刷呼出処理をしなければなりません。<br />
<br />
コード3は印刷を呼出す処理のイメージです。<br />
<br />
<h4>
コード3</h4>
<pre class="code-sample">// どこかにある全印刷関数
void printAll()
{
// 名刺の宛名印刷
for(int i = 0 i < nameCards.size() ; ++ i)
{
if(nameCards[i].isPrint())
nameCards[i].print();
}
// 会社の宛名印刷
for(int i = 0 i < companys.size() ; ++ i)
{
if(companys[i].isPrint())
companys[i].print();
}
}
</pre>
そっくりな処理が2つあります。どのように合理化すればいいでしょうか。<br />
<br />
似たような処理は1つにしたいし、2つのクラスはとてもよく似ていて、違いは僅かです。<br />
共通部分をうまくまとめることができれば、この2つの問題は一気に解決できます。<br />
<h3>
大切な継承という考え方</h3>
ここでとても重要な考え方が登場します。それは、<b>クラスの継承</b>です。クラスに、言わば親子に似た関係を持たせることができるのです。<br />
より一般的なクラスから、より特殊なクラスに対して継承する、という関係です。<br />
<br />
たとえば、「人」というクラスを継承して「男」や「女」というクラスを定義します。<br />
このとき、「男」は「人」である、「女」は「人」であるという言い方が成立します。<br />
もし、継承関係を逆にしてしまうと、「人」は「男」である、という間違った関係になってしまいますね。継承元のクラスの方がより一般的、継承先はより特殊である、ということになります。(継承は1階層だけでなく、何階層にも行うことが可能です)<br />
<br />
言葉がいくつかありますので、なんとなく覚えておいてください。<br />
「スーパークラス」を継承して「サブクラス」を作る。<br />
「基底クラス」(=スーパークラス)を継承して「派生クラス」(=サブクラス)を作る。<br />
「汎化」は派生クラスから基底クラスを導き出すこと。<br />
逆に、「特化」は基底クラスから派生クラスを導き出すこと(=継承)。<br />
<br />
C++には更に高度な?継承が可能です。多重継承と言って、複数の親を持つことができるのです。そうすることによって、複数の親の特徴を子が継承することになります。<br />
(C++以外のプログラミング言語では多重継承ができないものも多いです。C#もできませんが、それでは困るので別な方法が用意されています。)<br />
<br />
今回は「男」と「女」の共通点から「人」を導出する、という継承とは逆の手順(汎化)で、より一般的なクラスを導き出してみましょう。しかも、それは印刷機能にとって都合の良いものでなければなりません。<br />
<br />
<h4>
コード4</h4>
<pre class="code-sample">// ★印刷住所クラス
class PrintAddress
{
string m_name; // 宛名
string m_honorificTitle; // ★敬称
string m_address; // 住所
string m_tel; // 電話番号
string m_email; // メアド
protected:
bool m_isPrint; // 印刷フラグ
public:
// 印刷フラグのセット/リセット(パラメータを指定しない場合はtrue)
void setPrintFlag(bool isPrint = true){ m_isPrint = isPrint; }
// 印刷フラグの取得
bool isPrint(){ return m_isPrint; }
// 印刷処理
virtual void print() = 0;
};
// 名刺クラス
class NameCard : public PrintAddress // ★継承
{
string m_mobTel; // 携帯番号
string m_title; // 肩書き
string m_date; // 名刺交換日
string m_memo; // メモ
vector<Company *> m_pCompanys; // 所属会社 0~n
public:
// 印刷処理
void print();
};
// 会社クラス
class Company : public PrintAddress // ★継承
{
string m_department; // 部署
vector<NameCard *> m_pNameCards; // 社員 0~n
public:
// 印刷処理
void print();
};
</pre>
基底クラスとして印刷住所クラス(PrintAddress)を追加し、名刺クラスと会社クラスはそれを継承するようにしてあります。<br />
<br />
新たに登場する文法もいくつかありますが、このクラス構成にしたときのメリットを先に示しましょう。<br />
次のコードは print() を呼出す部分と、印刷用データ配列の作成のイメージです。<br />
<br />
<h4>
コード5</h4>
<pre class="code-sample">// 印刷データポインタの配列
vector<PrintAddress *> printAddresses;
// 名詞クラス、会社クラスのインスタンスを印刷データ配列に登録する処理の例
void func()
{
NameCard nc;
// NameCardのポインタをPrintAddressのポインタへキャストして登録
printAddresses.push_back((PrintAddress *)&dc);
Company cp;
// CompanyのポインタをPrintAddressのポインタへキャストして登録
printAddresses.push_back((PrintAddress *)&cp);
}
// どこかにある全印刷関数
void printAll()
{
// 宛名印刷
for(int i = 0 ; i < printAddresses.size() ; ++ i)
{
if(printAddresses[i]->isPrint())
printAddresses[i]->print();
}
}
</pre>
printAll() 関数は1つのループだけになっています。何故こんなことができるのでしょうか。<br />
<br />
1つ目の理由は、同じ1つの配列に NameCard と Company のインスタンス(のポインタ)が登録されているからです。その配列は printAddresses ですが、これは PrintAddressクラスのポインタを要素とする配列として定義されています。この場合、PrintAddressを継承したクラスのポインタを PrintAddressポインタにキャストすることができるので無理なく登録ができるのです。<br />
<br />
では、PrintAddresses[i]->print() は何を呼出すのでしょうか。普通に考えると、PrintAddressクラスの print() が呼ばれます。しかし、PrintAddressクラスの定義を見ると分かるのですが、virtual というキーワードで修飾されています。これは仮想関数と言って、継承先のクラスに同じ関数があった場合、そちらが優先して呼ばれる仕組みになっているのです。ただし、これはポインタを経由したときだけこの機能が働くことに注意してください。だから printAddresses の要素はポインタになっています。<br />
<br />
ここでまた是非覚えて欲しい言葉が出てきます。それは<b>ポリモフィズム</b>です。日本語で多態とも言います。外から同じ処理を呼んでも、実体に合わせて異なる処理が実行されるという意味になります。C++ のポリモフィズムは、このようにポインタを使用して実現します。<br />
<br />
それから、基底クラスである PrintAddressクラスの print() 関数には = 0 と付いていて、これは実装がないことを意味しています。純粋仮想関数とも言います。この場合、これはこういう関数が継承先にありますよ(または、継承先で実装してください)、という宣言ということになります。<br />
そして、この純粋仮想関数が1つでもあるクラスは、それ自身をインスタンス化することができません。このようなクラスのことを抽象クラスと言います。 <br />
<br />
PrintAddressクラスに protected というキーワードがあります。これは継承したクラスのメンバからはアクセスできるが、外部からはアクセスできないという意味のキーワードです。private だと継承したクラスからもアクセスできませんし、public では外部から無制限にアクセスされてしまいます。<br />
<br />
継承の書き方はこの通りに覚えておけば問題ありません。何故、public というキーワードが必要なのか、今は気にしないでください。<br />
<br />
今回は複数の似たクラスから汎化したクラスを導き出しました。これは、2つのクラスに印刷するための共通のインタフェースを追加する、という意味合いを含んでいます。この考え方はクラス設計では大切な考え方、概念ですので、是非、理解してください。<br />
<br /></div>
<h3>
コラム:クラス設計のアプローチ(重要)</h3>
クラスの設計をオブジェクト指向のべき論から入る場合もあります。実装のことはひとまず後回しにするのですね。<br />
<br />
今回のような場合だと、名刺クラスではなく自然人クラス、会社クラスは法人クラス。そして、それらの基底クラスを人クラスにする、という風に、コンピュータ内部ではなく、リアル世界に即したクラス構成にして、ある意味大風呂敷を広げてしまう訳です。<br />
<br />
もちろん、アプローチとしては特に間違っていませんが、やや遠回りをしすぎてしまう可能性もあります。この記事の説明は、名刺管理の名刺や、機能に着目したアプローチで、現場のプログラマにはとっつきやすいものだと考えています。<br />
<br />
いずれのアプローチでも、最終的にたどり着くところが同じであれば、僕は構わないのかな、と思っています。ただし、名刺管理のような比較的シンプルなアプリケーションではなく、多種のデータ種類を扱ったり、ユーザインタフェースも複数の系統があるようような、比較的大規模な場合、特に外側からのアプローチは大切になってきます。ただし、そのアプローチは機能設計の段階を経る、と言うことが前提にはなってきます。オブジェクト指向は必ずしもプログラミングだけのものではないのです。<br />
<br />
いずれにしても、オブジェクト指向の考え方に慣れてくれば、両方のアプローチで考え、双方の落としどころを探る、という技も身についてくるはずです。<br />
<br />
<br />T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-83520360222817211032018-01-19T14:21:00.001+09:002019-02-01T11:30:13.615+09:00Cプログラマのための、C++オブジェクト指向簡単裏口入門(7)<h2>
オブジェクトはどのように対象を写像するのか?(1)</h2>
<div>
名刺管理のアプリケーションの設計で考えていきましょう。<br />
要点以外の詳細はできるだけ省きます。<br />
<br />
オブジェクト指向の設計では、何がオブジェクトか、その全てを洗い出すところから始める、というのが常套手段です。でも、今回は設計手順を学ぶ記事ではないので、自由に考えていきます。<br />
<br />
名刺管理ですから、当然名刺がオブジェクトになると考えてみます。では、名刺クラスを作りましょう。<br />
<br />
<h4>
コード1</h4>
<pre class="code-sample">// 名刺クラス定義
class NameCard
{
string m_name; // 氏名
string m_mobTel; // 携帯番号
string m_email; // メアド
string m_title; // 肩書き
string m_company; // 所属会社
string m_department; // 部署
string m_address; // 会社住所
string m_tel; // 電話番号
string m_date; // 名刺交換日
string m_memo; // メモ
};
</pre>
とりあえず、名刺に記載されている情報と、名刺交換日とメモを加えてみました。<br />
処理についてはまだ当分は後回しにします。<br />
<br />
ところで、この名刺管理アプリにはどのような機能があるのでしょうか。(もちろん、本来であれば機能要件は先に決まっているはずです)<br />
<br />
<ul>
<li>検索機能(入力項目を条件にした絞り込み検索)</li>
<li>宛名印刷(宛名ラベルやはがきの宛名印刷)</li>
</ul>
<br />
今回はこの2つの機能があると仮定します。<br />
<br />
このうち宛名印刷は、個別の名刺宛に書類の送付等用以外に、年賀状等の一斉送付用も含めます。<br />
<br />
さて、ここで機能上の問題が考えられます。<br />
<br />
このクラスだと名刺1枚ずつをそのまま入力することになります。しかし、同じ会社に所属している人の名刺もたくさんあります。同じ会社なのに、毎回入力することになります。<br />
とすると、年賀状を各所属会社に一通ずつ送付したいと思っても、所属が一括管理されていないので難しいというのはご理解いただけるでしょうか。<br />
入力が正確で、一言一句、スペースの入力や全角半角の文字まで全て一致していて、なおかつ入力ミスが無ければ印刷するときに動的に名寄せする(重複を1つにまとめる)ことも可能ですが、人が入力する以上正確性は当てにできません。<br />
<br />
この問題には印刷時だけでなく、所属会社の事務所が移転する等修正が必要になった場合等にもぶつかります。<br />
<br />
そこで、所属する会社を分けて管理することが思いつきます。<br />
<br />
<h4>
コード2</h4>
<pre class="code-sample">// 名刺クラス
class NameCard
{
string m_name; // 氏名
string m_mobTel; // 携帯番号
string m_email; // メアド
string m_title; // 肩書き
string m_date; // 名刺交換日
string m_memo; // メモ
Company * m_pCompany; // 所属会社(NULLで所属無し)
};
// 会社クラス
class Company
{
string m_name; // 会社名
string m_department; // 部署
string m_address; // 住所
string m_tel; // 電話番号
};
</pre>
会社クラスが新たにできました。そして、名刺クラスには会社クラスのオブジェクトを1つ所有できるようにしています。<br />
これで概ね良さそうですか?<br />
<br />
でも、もしこの名刺管理アプリを僕が使うのだとしたら困ることがあります。<br />
同一人物が複数の会社やサークルなどに属していて、その人から所属の異なる名刺を複数枚もらうことがあるからです。<br />
会社は名寄せした状態で管理できるようになりましたが、今度は人物です。<br />
<br />
次のように解決しましょう。<br />
名刺クラスと会社クラスの関係が、名刺クラス側から見た場合、今は1:0or1ですが、これを1:0~nとすることです。<br />
<br />
ここで、ついでにもう1つ解決しておきたいことがあります。所属無しの名刺の場合(自由業など、個人名刺の場合)、連絡先住所などの入力がなくなってしまうので、名刺クラスの方にも住所などが必要になるということです。<br />
この項目は個人の自宅住所になりますが、通常名刺には書かれていませんね。しかし、親交が深まる等で個人宅当てにお歳暮を贈る、ということは良くあることなので、会社などに所属している場合でも利用できます。。<br />
<br />
では、コードを修正してみましょう。<br />
<br />
<h4>
コード3</h4>
<pre class="code-sample">// 名刺クラス
class NameCard
{
string m_name; // 氏名
string m_mobTel; // 携帯番号
string m_email; // メアド
string m_address; // ★1 住所(通常は自宅)
string m_title; // 肩書き
string m_date; // 名刺交換日
string m_memo; // メモ
vector<Company *> m_pCompanys; // ★2 所属会社 0~n
};
// 会社クラス
class Company
{
string m_name; // 会社名
string m_department; // 部署
string m_address; // 住所
string m_tel; // 電話番号
};
</pre>
★1の部分、名刺クラスに住所項目を追加しました。<br />
★2は、vector という動的配列で所属会社を複数登録できるようにしました。もちろん、要素数0も可能です。<br />
<br />
ところで、ここまであえて書かなかったのですが、クラスをどのように実体化してプログラムの中に持つのでしょうか。疑問に思われた方もいるかも知れません。<br />
<br />
個人も所属先の会社も名寄せした状態(重複させない)のですから、それぞれが配列のように実体を保持する状態だ、というところまではよろしいでしょうか。今はデータベースのことは考えず、メモリ上に全てのデータがあると考えてください。<br />
<br />
<h4>
コード4</h4>
<pre class="code-sample">vector<NameCard> nameCards;
vector<Company> companys;
</pre>
単純に、それぞれは vector 配列でこんな感じで保持されていることになります。<br />
<br />
NameCardクラスには Company クラスのポインタを要素とする配列の項目がありますね(コード3)。ここに、コード4の companys の要素のうち、所属するもののポインタを保持することになるわけです。<br />
<br />
これで、とりあえず多対多が成立はするのですが、会社クラスには関連付けの項目が無いので、ある会社に着目して、所属社員全てを見たいときは、このままだと nameCards を全て洗い出さなければなりません。これでは非常に処理に時間を要してしまいますので、やはり、会社クラスの方にも関連付けのための項目を持たせておいた方が都合がいいですね。<br />
<br />
<h4>
コード5</h4>
<pre class="code-sample">// 名刺クラス
class NameCard
{
string m_name; // 氏名
string m_mobTel; // 携帯番号
string m_email; // メアド
string m_address; // 住所(通常は自宅)
string m_title; // 肩書き
string m_date; // 名刺交換日
string m_memo; // メモ
vector<Company *> m_pCompanys; // 所属会社 0~n
};
// 会社クラス
class Company
{
string m_name; // 会社名
string m_department; // 部署
string m_address; // 住所
string m_tel; // 電話番号
vector<NameCard *> m_pNameCards; // ★社員 0~n
};
</pre>
会社クラスに社員項目を追加しました。<br />
<br />
これで、NameCard 側からも Company 側からも所属が瞬時に判る訳です。もちろん、しっかりと整合性を持たせて、どちらかの片思いの状態にしてはいけません。<br />
<br />
ただし、データの保持にデータベースエンジンを使用する場合、これらのクラスは一時的な使用に留まるため、関連付けの項目自体不要になる可能性もあります。関連付けはDB上で行われていて、関連のあるデータのみを読み出す場合が多いかもしれないからです。つまり、両方とも関連のあるデータしかメモリ上に存在しない状態であれば、関連付け情報自体必要が無いというわけです。<br />
あるいは、もし関連情報が必要になった場合でも、その時点でデータベースに検索させる、ということも十分考えられます。<br />
<br />
<span style="color: #38761d;">ところで、そろそろ説明に窮屈さを覚えてきましたので、1つ重要な用語を覚えてください。</span><br />
<span style="color: #38761d;">今まで、漠然とオブジェクト、という言葉を使ってきましたが、「インスタンス」という言葉を登場させます。</span><br />
<span style="color: #38761d;"><br />
</span> <span style="color: #38761d;">インスタンスとは、クラスを実体化したオブジェクトのことです。クラスがハンコだとすると、それで押した印影がインスタンスです。クラスからインスタンスを作ることをインスタンス化すると言います。つまり、インスタンスは狭義の意味でのオブジェクトと言えます。</span><br />
<span style="color: #38761d;">お堅いオブジェクト指向用語の1つですが、現場でもよく登場する言葉なので是非、覚えておいてください。</span><br />
<br />
先ほどのデータベースの話に戻りますと、データベースから特定のデータを読み出してきたものをクラスのインスタンスに保持する、という言い方になります。<br />
<br />
<br />
今回はここまでにします。<br />
しかし、実はまだまだこれらのクラスには合理化できる余地がたくさん残されています。次の記事以後説明していきます。<br />
<br /></div>
T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-20964883180249754262018-01-16T23:08:00.000+09:002019-02-01T11:29:51.640+09:00Cプログラマのための、C++オブジェクト指向簡単裏口入門(6)<h2>
変化に寄り添えるオブジェクト指向</h2>
<div>
大前提として、システム開発というものは一度の開発サイクルで終わることは希である、という点は確認しておきましょう。</div>
<div>
バグのないシステムを構築したとしても、ユーザが使っているうちに問題点や改善点が見えてきます。そして機能の修正や追加が繰り返されて完成度が増していきます。これは悪いことではないし、成功しているシステムはそういうものです。</div>
<div>
<br /></div>
<div>
ソフトウエアはハードウェアに比べて変更しやすいというのは、これはもう抗えない宿命です。つまり、システム開発は潜在的に変化に追従していく義務があるのだと思うのです。</div>
<div>
<br /></div>
<div>
そこで、ソフトウエアの品質を計る要素の1つ、<b>「保守性」</b>が登場するわけです。</div>
<div>
<br /></div>
<div>
ここで言う保守とは、機能を維持する、という消極的なものだけではなく、システムの改築までを視野に入れたより広い範囲を指します。</div>
<h3>
保守性に問題があると</h3>
<div>
初版のプログラムは概ね設計通りにできたとしても、何度も改造を繰り返すうちにプログラムコードは時間つぶしには最適の迷路パズルゲームと化し、その難易度は高くなる一方で、最後には手が付けられなくなって開発サイクルはすぐに終焉を迎えてしまいます。<br />
<br />
ありがちですが、どうしてそうなってしまうのでしょうか。<br />
<br />
1つには予算の問題が挙げられます。<br />
システムの改修にかかる費用が割高に思えるのは、新車の購入金額の割に修理の部品代が妙に高く感じられるのと似ています。車1台分の部品をバラバラに購入したら新車が何台買えることか・・・?<br />
車の部品には定価があるので、工賃を少し負けてもらうくらいしかできません。<br />
しかし、ソフトウエアの改修は基本は全てが人的工数ですから、どうしても無理をさせられてしまいがちです。<br />
無理をした結果、改修の繰り返しは悪循環と化し、地獄へ落ちていくのですね。<br />
<br />
しかし、実際にプログラムの改修というのは、100%自由に作ることができる新規開発に比べて工数がかかるのは仕方がない面もあります。<br />
<br />
なので、これをいかに軽減できるか、そこが焦点になります。<br />
<h3>
オブジェクト指向でどのようにこの問題を解決するのか</h3>
理想はプログラム改修の工数が、その機能改修によって生み出される価値と一致するか、更にそれを下回るようにすることです。<br />
<br />
しかし、オブジェクト指向は決して魔法ではありません。これを取り入れたからといって、一気に理想に近づける訳ではありません。しかし、そのポテンシャルは十分に備えていますから、習得の為の労は必ず報われるはずです。少なくとも、僕にはそう感じられるので、これまで楽しくオブジェクト指向プログラミングに取り組んで来ることができました。<br />
<br />
蛇足ながら、<b>「楽をするための労は惜しまない。」</b>という僕のスタンスにぴったりの技術でもあります(BLOG記事<a href="http://blog.explasm.jp/2017/12/1.html" target="_blank">『忘れられた合理性の追求』</a>を参照のこと)。<br />
<br />
こんなことを書くと、ここ以降は茨の道になるように誤解されるかも知れませんが、そんなことはないのでご安心ください。<br />
<br />
オブジェクト指向の神髄を紐解いていきましょう。<br />
<h3>
肝となる写像という考え方</h3>
<div>
オブジェクト指向の聖書(偉い人が書いた書籍)には、よく現実をモデル化したものをそのままクラス(オブジェクト)にする、といったような<b>迷</b>言が書かれています。<br />
我々プログラマはその持って回った言い方によく惑わされてしまいます。</div>
<div>
技術者ではないマネージャがこの話を聞くと、オブジェクト指向がまるで魔法のツールのような誤解をして我々技術者を困らせることもあります(実話)。</div>
<div>
<br />
現実にプログラムを対応させる、と言っても、コンピュータの中にはそれとは無関係なオブジェクトと呼べるものがたくさんあります。ファイルシステムやディレクトリ、ファイル、データベース、メモリやネットワーク等々。プログラムが意識する対象の多くはそんなものが中心だったりしますね。<br />
<br />
現実をモデル化したもの、それはたとえば「人」。人というオブジェクトが、コンピュータ内部に特有のオブジェクトと同列に存在することに、違和感を覚えずにはいられませんね。<br />
<br />
でも実は、そこは階層を分けて考えればいいだけなのです。簡単な話でした。<br />
簡単だけど、この説明を直接書いてある書籍に出会ったことはありません。<br />
<br />
<br />
ただし、この聖書に書かれている言葉にはとても大切なことが隠されています。<br />
それは、オブジェクトが対象を良く表したものであることが求められるという点です。対象が現実社会の何かをモデル化したものであっても、あるいはコンピュータの内部のことであったとしても同じです。<br />
オブジェクトはプログラムが扱う「もの」であって、対象そのものではありません。<b>対象に対して、オブジェクトは写像である</b>と考えます。<br />
<br />
写像であれば、その元になる対象に変化があっても、写像も同じように変化させればよく、回りくどい修正は必要無いはずである、と考えるのですね。<br />
<h3>
従来型のプログラムの問題点</h3>
逆に、オブジェクト指向ではない従来のプログラムではどうだったかを思い出してみましょう。<br />
<br />
まとまりのあるオブジェクトという構造は存在せず、対象を操作する関数だけがありました。ひょっとしたら関数の操作対象は1つだけではなく、同時にいくつかの対象を操作するものかも知れません。その方が都合が良かったのでしょうね。<br />
<br />
そこで、1つの対象に変化があり、プログラムがその変化に対応しなければならなくなったとします。<br />
<br />
まず、その対象の変化により、プログラムのどの部分に影響があるか、その全てを調べるのに大きな労力が必要です。あちこちにそれを操作する関数が分散しているからです。<br />
そして、操作対象が複数になっている関数やプログラムの場合、無関係な対象に影響が出ないように慎重に行う必要があり、これまた膨大な時間がかかってしまうことになります。<br />
<br />
オブジェクト指向という筋の通った考え方に沿ったプログラミングから見ると、従来型のプログラムには節操がないように見えてしまいます。<br />
<br />
<br />
今回は具体的なプログラミングの例を示すことができませんでしたが、次回からは具体的なオブジェクトのプログラミングに入っていきたいと思います。<br />
<br /></div>
<h3>
コラム:オブジェクト指向の習得</h3>
<div>
<a name='more'></a>僕は、C言語の習得にはさほど時間を要しませんでした。本を読んで、割とすぐに使えるようになりました。</div>
<div>
それは、関数というサブルーチンで機能分担していくという考え方がとてもシンプルで解りやすかったからだと思います。(ポインタについてはそれ以前にアセンブラも学んでいたので、さほど難しいとは感じませんでした)</div>
<div>
<br /></div>
<div>
しかし、C++は本を読んでもいきなり使いこなせるところまで行きませんでした。C++はCを含んでいるので、動くものにすること自体は可能でしたが、オブジェクト指向プログラミング、という観点ではそう易々と習得できるものではなかったのです。</div>
<div>
<br /></div>
<div>
それには理由があります。オブジェクト指向をパズルに喩えると、その解は1つではなく、実際にかなりの数があります。無機的に数えてしまえば従来の構造化プログラムであっても無数にある、としか言えません。そうではなくて、概ね妥当であると判断できる正解がいくつも存在するという意味なのです。</div>
これは、主にオブジェクト指向におけるクラス設計について言えることです。<br />
<br />
クラス設計をするというのは、外国語の会話を習得するのに似ています。文法を覚えたり、単語を暗記しただけでは外国人との会話はできません。会話の訓練が必要です。オブジェクト指向のクラス設計にもそのような訓練が必要だと考えています。<br />
<br />
それを誤解し、何冊か本を読んでも使えるようにならず、諦めてしまう人も多いのかも知れないですね。<br />
<br />
しかし、僕にはクラス設計の妥当性を考えることはとても楽しいと感じられます。このことで人と議論することは、ソフトウエア開発の醍醐味の1つだと言えます。<br />
是非、読者の方にもその楽しさを感じられるところまでになって欲しいと願います。オブジェクト指向開発に完全なゴールはないかも知れませんが、その楽しさを感じられるようになることは、大きな目標としていいと思うのです。<br />
<br /></div>
T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-6478602062811791702018-01-14T21:18:00.000+09:002019-02-01T11:29:34.570+09:00Cプログラマのための、C++オブジェクト指向簡単裏口入門(5)<h2>
プライバシー問題!?</h2>
<div>
とてもソフトウエア工学とは直接関係なさそうなタイトルですね。でも、実際にはプログラミングにとって非常に重要な考え方で、是非、これは覚えるだけではなく、身につけて今後に活かしていただきたいと願います。<br />
特に、C言語だけを何の問題も感じずに長年やってきた人にとっては、ひょっとすると面倒臭い余計なものに感じられるかも知れませんので、<span style="color: red;">注意</span>が必要です。<br />
<h3>
実社会のプライバシー問題</h3>
実社会でも、以前の日本ではプライバシー問題という言葉すらほとんど聞くことはありませんでした。<br />
ところが、時代が新しくなるにつれ、人間関係が疎になっていったし、習慣の違ういろんな人が集まるようになってくると、赤の他人に必要以上の個人情報を知られて悪用されることに、社会全体が敏感になっていったのです。<br />
<h3>
プログラミングのプライバシー問題?</h3>
プログラミングも昔はプライバシー問題に鈍感でした。パーソナルコンピュータが一般に普及し始めた頃、おまけで付いていたインタプリタ言語(N88BASIC)にはグローバル変数しかありませんでした。<br />
<br />
しかし、多機能、高機能が求められ、プログラムが複雑、かつ大きくなってくると限界が見え始めます。そんなときに構造化プログラミングの必要性が浸透すると共に、C言語が普及し始めました。関数という単位でプログラミングをし、関数の中だけで使える変数がある。それだけでもプログラマにとってとてもありがたく、助かりました。<br />
<h3>
オブジェクトのプライバシー</h3>
ハードウェアの進化と共に、ソフトウエアに求められる機能はより多く、より高度になっていきました。当然プログラムは複雑、かつ巨大にならざるを得ません。<br />
<br />
この問題解決の一助となったのがオブジェクト指向プログラミングです。<br />
<br />
これまで、プログラミングにおけるオブジェクトとは、ただのデータ構造でしかありませんでした。関数などの処理コードがまずあって、そのコードが操作する対象がデータ構造でした。<br />
オブジェクト指向ではこのデータ構造を主体に考えます。そして、コードはデータ構造に従属するという考え方に変わります。つまり、データ構造とコードが非常に密接に関連付けられることになったのです。<br />
<br />
その、オブジェクト指向で言うところのオブジェクト、つまり、そのデータ構造に従属する専用の処理コードもひっくるめてのオブジェクトは、これまでの関数という単位よりも高い独立性を確保することができます。<br />
<br />
独立性が高いということは、逆に依存度は下がります。その結果、より汎用的で応用範囲も広がり、プログラムの価値も高くなります。<br />
<br />
そんな独立性を高めるための機能をご紹介します。(やっとですね!)<br />
C++では、オブジェクトの独立性をどうやって確保するのでしょうか。前回のサンプルコードをもう一度見てみましょう。<br />
<br />
<h4>
コード1</h4>
<pre class="code-sample">struct Point // データと処理が一体となっている構造体
{
int m_x, m_y;
void set(int x, int y);
};
// Pointのメンバ関数
void Point::set(int x, int y)
{
m_x = x; // 構造体メンバm_xに設定
m_y = y; // 構造体メンバm_yに設定
}
int main()
{
Point p;
p.set( 100, -200 );
reutrn 0;
}
</pre>
実はこのコードには問題があります。それは、次のようにメンバ変数を直接どこからでもアクセスできてしまうところです。<br />
<br />
<h4>
コード2</h4>
<pre class="code-sample">struct Point // データと処理が一体となっている構造体
{
int m_x, m_y;
void set(int x, int y);
};
// Pointのメンバ関数
void Point::set(int x, int y)
{
m_x = x; // 構造体メンバm_xに設定
m_y = y; // 構造体メンバm_yに設定
}
int main()
{
Point p;
p.m_x = 100; // C言語と同じように直接メンバ変数を変更できてしまう。
p.m_y = -200;
reutrn 0;
}
</pre>
現実的にこのような単純な構造体でしたら直接的な問題はないのかも知れません。<br />
しかし、たとえば分数を表す構造体で、分母に0を設定できないというルールがあった場合はどうでしょうか。<br />
<br />
<h4>
コード3</h4>
<pre class="code-sample">struct Fraction // 分数
{
int m_numerator; // 分子
unsigned m_denominator; // 分母
bool set(int numerator, unsigned denominator);
double getDouble();
};
// メンバ関数(戻り値は成功/不成功)
bool Fraction::set(int numerator, unsigned denominator)
{
if(denominator == 0) // 分母に0は設定できない
return false; // エラーを返す
m_numerator = numerator;
m_denominator = denominator;
return true;
}
// これもメンバ関数
double Fraction::getDouble()
{
// これは分母が0だと困ります
return (double)m_numerator / (double)m_denominator;
}
int main()
{
Fraction frt;
if(! frt.set( -2, 3 ))
return 1;
frt.m_denominator = 0; // ★やばい!
printf("%f\n", frt.getDouble()); // ここで0除算で落ちるね(;;)
reutrn 0;
}
</pre>
せっかく、構造体専用の関数set()で分母が0の場合をエラーにして設定できないようにしていたのに、直接メンバ変数を操作されてしまえば意味がありません。<br />
さて、どうしましょうか。コーディング規約に「メンバ変数を直接操作しないこと!」の一行を追加しましょうか。でも、デバッグ用の特例コードで変数を操作して、うっかりそのコードを本番に残してしまったら? 大量のコードからどうやって見つけますか? ひょっとしたら、m_denominator という変数名は他でも使っているかも知れません。すると、grep機能を使ってもなかなか見つけられませんね。<br />
<br />
C++にはオブジェクト指向の隠蔽という考え方を反映させた機能が最初から組み込まれています。コード3にそれを適用したものがコード4です。<br />
<br />
<h4>
コード4</h4>
<pre class="code-sample">struct Fraction // 分数
{
private: // <b>プライベート宣言をして、隠蔽する</b>
int m_numerator; // 分子
unsigned m_denominator; // 分母
public: // <b>公開することを宣言する</b>
bool set(int numerator, unsigned denominator);
double getDouble();
};
// メンバ関数(戻り値は成功/不成功)
bool Fraction::set(int numerator, unsigned denominator)
{
if(denominator == 0) // 分母に0は設定できない
return false; // エラーを返す
m_numerator = numerator;
m_denominator = denominator;
return true;
}
// これもメンバ関数
double Fraction::getDouble()
{
// これは分母が0だと困ります
return (double)m_numerator / (double)m_denominator;
}
int main()
{
Fraction frt;
if(! frt.set( -2, 3 ))
return 1;
frt.m_denominator = 0; // ★★コンパイルエラー!
printf("%f\n", frt.getDouble());
reutrn 0;
}
</pre>
構造体にキーワード「private」と「public」が追加されています。private: 以後は外部からのアクセスができないメンバ(変数も関数も)になります。(もちろんメンバ関数からは自由にアクセスできます)<br />
public: 以後は公開メンバとなります。<br />
その結果、main()関数の中で直接プライベートメンバに値を設定しようとするところでコンパイルエラーになってしまいました。<br />
<br />
このように外部に晒したくないメンバを隠蔽することで、不要なアクセスをされる心配がなくなります。アクセスされないことが保証されるわけです。そのことにより構造体がオブジェクトとして独立性を高められる、ということになるのです。<br />
<br />
他人の家には、玄関を通してのみ、許可を得て出入りするのと一緒ですね。その家の裏口も窓も開けっぱなしでは、いつ誰が入ってくるか判りません。周辺の住人を信用しているかいないかの問題ではなく、そういった疑いを持たずに済む、ということが重要なのです。<br />
<h3>
クラスの登場</h3>
ここまで、C言語にもある構造体を使って説明してきました。しかし、C++では上記の例のような場合は struct ではなく、class を使うのが一般的です。クラスというのはオブジェクト指向の用語でもあります。実は、C++では struct もクラスです。違いはただ1点だけです。<br />
それは、デフォルトが private か public かの違いだけです。class と struct、どちらがどちらでしょうか。上記のコードを見ていただければ判ると思いますが、class はデフォルトが private、つまり、public: が現れる前は private 扱いになり、struct はその逆です。<br />
<br />
<h4>
コード5</h4>
<pre class="code-sample">class Fraction // 分数クラス
{
int m_numerator; // 分子(<b>プライベートメンバ</b>)
unsigned m_denominator; // 分母(<b>プライベートメンバ</b>)
public:
bool set(int numerator, unsigned denominator);
double getDouble();
};
</pre>
違いはそれだけなのですが、慣例として struct を使うのは受動的なデータ構造を扱う場合が多いです。つまり、C言語で使っていたようなデータ構造を示すだけの構造体的なものの場合ですね。<br />
<br />
オブジェクト指向の用語でもあるクラスを示す class のデフォルトが private なのは意味があります。公開は必要最小限にして、独立性を高め、プライバシーを強く意識することが信頼性を高めるために大切だからです。<br />
<br />
更に、家の玄関を通常は1つにするように、インタフェースはできるだけシンプルにするという意味もあります。<br />
<br />
<h3>
コラム:隠蔽とは?</h3>
<a name='more'></a><br />
隠蔽という言葉はオブジェクト指向での用語でもありますが、やや誤解されがちな言葉です。<br />
C++では通常ヘッダファイル(*.h)に class を定義することが多いのですが、クラスを使ったライブラリ(クラスライブラリ)をバイナリで提供しようとしても、ヘッダファイルにクラスで使用している変数名が見えてしまいます。privateであっても、人には見えてしまいます。それで、隠蔽できないじゃないか、と言うわけです。<br />
<br />
もちろん、オブジェクト指向での隠蔽は他のプログラムから見えない(アクセスできない)という意味ですから、人の目に見える見えないとは直接関係はないのです。<br />
しかし、ライブラリを提供されて、なにかちょんぼ?をしようとして、ヘッダファイルのprivateをpublicに書き換えて変数に直接アクセスしよう、と思えばできてしまうかも知れません。<br />
それを避ける方法はC++自体にはありませんが、プライベートメンバーは void *だけにして、そこに実行時に意味のあるオブジェクトを動的に割り当てる、といったやり方等でやれないことはないでしょう。<br />
しかし、現実的にそれが必要になることはほとんど無いようには思います。あるとすれば、かなり特殊な事例だと思われます。<br />
<br /></div>
T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-57323855502015723822018-01-12T20:37:00.000+09:002019-02-01T11:29:13.725+09:00Cプログラマのための、C++オブジェクト指向簡単裏口入門(4)<h2>
指向!?</h2>
<div>
前回、C++でのオブジェクトは実際にはメモリ上の構造に過ぎないことを示しました。では、そんなオブジェクトに対する指向とはどういうことなのでしょうか。単なるメモリ上の構造にしては、少々大げさな表現に思えますよね。<br />
<br />
しかし、本当のところ、ここにはパラダイムシフトと言える程の大きな変化が隠されています。にもかかわらず、その変化の基本は非常にシンプルです。<br />
<br />
オブジェクト指向ではないC言語では、メモリ構造に対する処理は通常次のようになります。<br />
<br />
<h4>
コード1</h4>
<pre class="code-sample">// C言語でのメモリ構造操作
typedef struct
{
int m_x, m_y;
} Point;
void setPoint(Point * pPoint, int x, int y)
{
pPoint->m_x = x;
pPoint->m_y = y;
}
int main()
{
Point p;
setPoint( &p, 100, -200 );
return 0;
}
</pre>
では、次にC++で<u>オブジェクト指向的</u>に書いてみます(C++でもCと同じスタイルで書けてしまうのでこういう表現をしましたが、今後特別な場合を除いてこの説明は省きます)。<br />
<br />
<h4>
コード2</h4>
<pre class="code-sample">// C++でのメモリ構造操作
struct Point // 今回はclassではなく、structとしておきます
{
int m_x, m_y;
void setPoint(int x, int y);
};
void Point::setPoint(int x, int y)
{
m_x = x; // 構造体メンバm_xに設定
m_y = y; // 構造体メンバm_yに設定
}
int main()
{
Point p;
p.setPoint( 100, -200 );
return 0;
}
</pre>
Cにはない書き方がありますね。<br />
1つは、構造体の中に関数 setPoint() のプロトタイプがあることです。これはこの構造体のメンバ関数と呼びます。変数をメンバ変数と言うのと一緒です。そして、そのプロトタイプの実装が Point::setPoint(){} といった関数名で書かれています。Point構造体のメンバ関数なので、「::」(コロン×2)で繋いでメンバ関数であることを表します。<br />
main()の中では、構造体のメンバ変数にアクセスするときと同じように「.」(ドット)を使ってメンバ関数にアクセスし、呼出しています。<br />
<br />
コード1も2も同じ機能を果たすプログラムですが、コード2のC++の方はsetPoint()の引数が1つ減っています。これは、メンバ関数の中から同じメンバに対してはグローバル変数のようにアクセスできるので、わざわざパラメータを必要としないためです。<br />
<br />
さて、これら2つのプログラムの本質的な違いは何でしょうか。関数を動詞に喩えると、<br />
<br />
<ul>
<li>Cの場合、<b><span style="color: #990000;">「~をする」</span></b></li>
<li>C++は、<b><span style="color: #990000;">「~が~をする」</span></b></li>
</ul>
<br />
と、なります。C++の方には主語があるのです。</div>
<br />
あれ、たったそれだけのこと? 目先が違うだけでは? と感じられた人もいるかも知れません。今後、徐々にその意義を理解して頂ければいいのですが、今回は概要を説明しておくことにします。<br />
<br />
~が~をする、というのは、オブジェクトと動作が強く結びつけられている、ということでもあります。そうすると何が良くなるのか、ユーザインタフェースを例に説明します。<br />
<br />
WindowsにしてもMacにしても、スマートフォンにしてもそうなのですが、GUIにはオブジェクト指向的なコマンドの起動方法が含まれています。<br />
<br />
たとえば、Windowsのデスクトップにワープロのドキュメントファイルがあり、それを編集するとき、皆さんはどうされますか? 多くの人はそのファイルをダブルクリックしますね。すると、結びつけられているワープロソフトがファイルを開いた状態で立ち上がります。ファイルが表計算ソフトのものであれば、表計算ソフトが同様に起動します。<br />
ダブルクリックではなく、マウスの右クリックでコンテキストメニューを開き、「編集する」というコマンドを選んでも同じ動作が可能です。また、右クリックでは印刷コマンドとか、プロパティを表示するといった、そのファイルで可能な別のコマンドも実行できるようになっています。<br />
<br />
この動作はユーザにとって極めて自然です。もし、GUIにおけるオブジェクト指向がなかったらどうでしょうか。編集するときは目の前にファイルが見えていても、一旦ワープロソフトを起動し、ワープロソフトからファイルを探してオープンする操作をしなければなりません。<br />
<br />
世の中のコンピュータがGUIになる以前は、全てコマンドプロンプトからプログラムを起動しなければなりませんでした。テキストを編集するコマンドが edt で、編集対象のファイルが test.txt であるなら、<br />
<br />
> edt c:\myfiles\test.txt<br />
<br />
等と入力しなければなりませんでした。コマンドも、対象ファイルもどちらも覚えておかなくてはならなかったのです。<br />
<br />
現在主流のGUIは単にマウスを使ってグラフィカルに操作するだけでなく、ファイルとコマンドの結びつきが定義されていて、そのファイルに対して何ができるのかといったことをユーザが考えたり、予め知らなくてもいいようになっています。<br />
オブジェクト(この場合はファイル)と動作(コマンドやアプリケーション)が密接に関連付けられている、という意味ではオブジェクト指向的なユーザインタフェースであると言えるのです。<br />
<br />
プログラミングに戻りましょう。<br />
<br />
上記コード1では、Point構造体とそれを扱う関数との間に直接の関連付けはありません。Point構造体だけ見ても、それに対するどんな処理が存在するのか不明です。不便ですね。上記のCUIのときと似た不便さがあります。<br />
逆に、コード2ではそれが明らかで、これもGUIの例になぞらえることができます。<br />
<br />
関数の側から見てみると、コード1ではsetPoint()という関数名はグローバル定義にするしかありません。なので、他の目的でも同じ関数名が必要になった場合、名前がぶつかってしまいます。なので、汎用的で意味の広い関数名は使いにくくなります。不便ですね。<br />
コード2の場合、同じメンバでなければ(同じメンバでも引数が異なれば可能)、同じ関数名がいくつあっても問題ありません。なので、シンプルで判りやすい関数名を使うことができます。<br />
ところで、コード1での関数名はPoint構造体に値を設定する、という意味でsetPoint()という関数名にしてありますが、コード2は関連付けが明らかなので、あえて関数名にPointを含める必要はありません。なので、この場合は単にset()とした方が良いでしょう(コード3参照)。<br />
<br />
<h4>
コード3</h4>
<pre class="code-sample">// C++でのメモリ構造操作
struct Point // 今回はclassではなく、structとしておきます
{
int m_x, m_y;
void set(int x, int y);
};
void Point::set(int x, int y)
{
m_x = x; // 構造体メンバm_xに設定
m_y = y; // 構造体メンバm_yに設定
}
int main()
{
Point p;
p.set( 100, -200 );
return 0;
}
</pre>
<br />
ここまでをまとめると、<br />
オブジェクトを主体に考えること(つまりオブジェクト指向)で、オブジェクトに対して何ができるのかがはっきりする。処理、という観点においても、オブジェクトが結びついていることで何に対する処理なのかが明確になる。ということになるでしょうか。<br />
<br />
そして、この判りやすさは使うときだけではなく、設計やコーディング時にも享受できるのですが、その点については今後徐々に理解して頂けると思います。<br />
<br />
<br />T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-59428865896209629072018-01-12T16:10:00.000+09:002019-02-01T11:28:49.604+09:00Cプログラマのための、C++オブジェクト指向簡単裏口入門(3)<h2>
オブジェクト!?</h2>
<div>
これまで、散々オブジェクト指向って聞かされてきたけれど、そのオブジェクトとは何なの? という疑問をお持ちでしょうか。それは、平たく言えば「もの」となります。C++ではこれをクラスというものを使って構築&表現します。<br />
<br />
おっと、既に話が飛躍したかも知れません。<br />
<br />
オブジェクト指向というのは、単にプログラミング以外にも、システムの概要的な設計の段階からその考え方を適用可能なので、一概にその「もの」が実際には何であるか、とは言い切れないところがあります。しかし、この記事はC++プログラミングに限定するので、それはメモリ上のある構造を指しているに過ぎない、と言えます。<br />
<br />
Cでは構造体に相当する、クラスというものがC++にはあります。キーワードもそのものズバリ、classです。そして、構造体を示すstructも、実はC++ではクラスなのです。違いはほとんどありません。ある意味、全く違わないとも言えます。(小さな違いについてはいずれまた)<br />
<br />
クラスそのものはオブジェクトではありません。オブジェクトの設計図です。これをインスタンス化したものがオブジェクトです。「インスタンス化」という言葉は是非ここで覚えてしまってください。次のコードのように使用します。<br />
<br />
<h4>
コード1</h4>
<pre class="code-sample">// まずはクラスの定義
class MyClass
{
int member;
};
void func()
{
MyClass object; // MyClassクラスをobjectという名前でインスタンス化
}
</pre>
このやり方は実はCでもありましたね。Cでは構造体になりますが、そっくりなコードになります。<br />
<h4>
コード2</h4>
<pre class="code-sample">// 構造体の定義(C言語)
typedef struct
{
int member;
} MyStruct;
void func()
{
MyStruct object; // これも実はインスタンス化
}
</pre>
ちなみに、C++ではsturctもclassと同様、typedefは不要です。<br />
是非、C++なのにstructにtypedefを使うような大人にならないでくださいね!?(何故か使う人が多い)<br />
<h4>
コード3</h4>
<pre class="code-sample">// 構造体の定義(C++)
struct MyStruct // structでも実際にはクラス。構造体の上位互換性がある。
{
int member;
};
void func()
{
MyStruct object;
}
</pre>
<br />
ここまでをまとめると、オブジェクト指向のオブジェクトの設計図がクラス、そして、そこからインスタンス化したものがオブジェクトになります。<br />
なので、C++でオブジェクトとは、メモリ上の構造でしかない、と覚えておいてほぼ間違いはありません。<br />
<br /></div>
T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-4922520145787726512018-01-10T21:42:00.000+09:002019-08-21T21:48:57.053+09:00Cプログラマのための、C++オブジェクト指向簡単裏口入門(2)<h2>
なぜ、オブジェクト指向なのか?</h2>
<div>
ソフトウエアの品質を上げようとすると、自然にオブジェクト指向にたどり着いた、と、僕自身は感じています。<br />
このシリーズを通じて、読者の方にも徐々に感じていただけたらと願っています。なのでこの問いの回答を最初に説明するつもりはありません。<br />
<br />
ただし、C++の機能を知っていく中でオブジェクト指向の必然性を感じていただくためのヒントとして、ソフトウエアの品質について少し説明させてください。<br />
<br />
<h3>
ソフトウエアの品質とは</h3>
<div>
<br /></div>
ソフトウエアの品質とは、主に<br />
<ul>
<li>機能性</li>
<li>安定性</li>
<li>保守性</li>
<li>コストパフォーマンス</li>
</ul>
から成っています。<br />
もちろん、それぞれの要素は完全に独立しているわけではなく、相互に強く関連し合っています。<br />
<br />
<b><span style="color: #674ea7;">[機能性]</span></b><br />
<div class="indent-1">
コーディング前のフェーズにはこれを大きく左右する要素が含まれますが、コーディングでは主に処理速度のことを指します。<br />
場合によっては安定性や保守性とトレードオフの関係になります。<br />
<br /></div>
<b><span style="color: #674ea7;">[安定性]</span></b><br />
<div class="indent-1">
動作の安定性のことで、バグが含まれないことが求められます。<br />
安定性と機能性は相反する場合がありますが、現在はハードウェアの性能が高くなった分を安定性に振り分ける余裕があるので、多くのプログラミングでこれがせめぎ合うケースは少ないと思われます。もちろん、リソースに余裕のないハードウェアをターゲットにしたり、処理速度に関する要求仕様が非常にシビアなジャンルもあり、全てが容易に両立できるわけではありません。</div>
<br />
<b><span style="color: #674ea7;">[保守性]</span></b><br />
<div class="indent-1">
機能追加やバグ修正のしやすさです。他の要素に比べて見落とされがちですが、非常に重要な要素です。<br />
機能性とはトレードオフの関係になる場合もありますが、安定性とはトレードオンの関係になる場合が多いと思われます。</div>
<br />
<b><span style="color: #674ea7;">[コストパフォーマンス]</span></b><br />
<div class="indent-1">
主に開発効率のことです。<br />
上記3要素が全て揃っても、システム開発に莫大なコストがかかってしまえば現実的には成立できず、システムは存在できなくなります。技術者であってもその点を意識し、開発効率を常に考慮する必要があります。<br />
<br />
ソフトウエアの生存期間をトータルで考えた場合、特に保守性、および安定性とはトレードオンの関係と言えます。機能性とは間接的にトレードオフの関係になります。<br />
更に、システム開発において1から10までを毎回全て新規開発する状況は好ましくありません。独立性が高く、汎用性のある部分をライブラリ化して無駄な工数を削減することはとても重要です。つまり、1つのシステムだけではなく、社内的な開発体制にまで視野を広げる必要があるということになります。</div>
<br />
<h3>
オブジェクト指向と品質の関係</h3>
<br />
ここでは具体的な説明は避けますが、概要だけお伝えしておきます。<br />
オブジェクト指向は品質の要素のうち、特に「安定性」、「保守性」、「コストパフォーマンス」に大きく寄与します。<br />
「機能性」については、実行速度の点でややマイナスになる可能性はありますが、少し俯瞰して見ると、その他の要素が向上することで余力が生まれ、それまでできなかったことができるようになるなどして、むしろプラスに作用する場合も多いはずです。<br />
<br />
<h3>
ソフトウエアの品質を意識する</h3>
<br />
ソフトウエアの品質を常に意識しながら読み進めてください。そして、実際のコーディング時にも常に意識することが大切です。<br />
<br />
コーディングのしやすさは開発効率に寄与しますが、場合によってはあまり目先のことに囚われるとトータル的には効率を落としてしまうことがあります。システムの開発サイクル全体を意識して判断することが求められます。<br />
<br />
<br /></div>
T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-18681158335514838972018-01-09T19:55:00.000+09:002019-02-01T11:28:14.182+09:00Cプログラマのための、C++オブジェクト指向簡単裏口入門(1)<h2>
はじめに</h2>
<div>
<b><span style="color: purple;"><br /></span></b>
<b><span style="color: purple;">「C++を学ぶ前に、まずはオブジェクト指向を学べ。」</span></b></div>
<div>
<br /></div>
<div>
この言葉を鵜呑みにして何やら詳しそうなオブジェクト指向の本を買ってみても、モデルがなんちゃらとか、なじみのない用語が登場していきなり疎外感に見舞われます。仕方なく章を飛ばすけれど、やはりC言語では比較的なじみのない業務系アプリケーションの設計方法が例として採り上げられていて、鬱陶しいやらめんどくさいやら。</div>
<div>
<br /></div>
<div>
ご立派なオブジェクト指向の本(聖書)には妙に遠回りさせられてしまうし、ひょっとしたら<u>制御系をやっている人にはあまり関係が無いんじゃないか</u>、等と都合の良い理由を付けて諦めてしまいます。</div>
<div>
<br /></div>
<div>
要は、オブジェクト指向の敷居は妙に高い。という、気にさせられてしまうわけですね。</div>
<div>
こんな環境が<a href="http://blog.explasm.jp/2014/03/blog-post_3436.html" target="_blank">オブジェクト指向ディバイド</a>(リンク先参照)を産んでしまうのでしょうか。</div>
<div>
<br /></div>
<div>
きっと、その要因はもっといろいろあると思いますが、まずはそんなことは忘れて、<span style="background-color: orange;">C++という言語を使って、より効率よく、より品質の高いプログラミングをする</span>、ということを目標に、まずはできることからやってみませんか。</div>
<div>
もちろん、これまでオブジェクト指向を学ぼうとしたことがなく、上記のような経験さえないという方も大歓迎です。</div>
<div>
<br /></div>
<div>
冒頭の言葉を言い換えるなら、</div>
<div>
<span style="color: blue;"><br /></span></div>
<div>
<b><span style="color: blue;">「C++を学ぶなら、オブジェクト指向もついでに学べ。」</span></b></div>
<div>
<br /></div>
<div>
と、なります。</div>
<div>
<br /></div>
<div>
もし、過去BLOGの <a href="http://blog.explasm.jp/2017/12/cc.html" target="_blank">Cプログラマのための、C++簡単裏道アプローチ</a> を読んでいないなら、できれば先に目を通しておいてください。</div>
<div>
そちらの記事はオブジェクト指向を避けてC++のメリットをお伝えするものでしたが、やはりそれには限界がありました。C++の本領を発揮するにはやはりオブジェクト指向を避けては通れなくなり、そこで今回の記事を書くことにしたのです。</div>
<div>
<br /></div>
<div>
本編は(2)から始めます。</div>
<div>
<br /></div>
T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-34990300905336786842018-01-08T13:58:00.000+09:002018-01-08T21:41:58.102+09:00奇跡 V.S. 偶然僕が小学1~2年生の頃、同じ団地に住む同級生の女の子のお父さんが突然亡くなった。<br />
<br />
このくらいの子どもは、死というものをあまり深刻に受け止めないようなところがある。大人のようにその後の諸々を考えて悲しみにうちひしがれることがない。<br />
<br />
お葬式を午後に控えた朝、小学校に向かいながら女の子と見た光景は、女の子の住む団地の部屋から空に向かって雲が道を作っていたことだった。少なくとも、僕らにはそう見えた。そして、「きっとちいちゃん(女の子の呼び名)のお父さんはあの雲の道で天国に行くんだね」と、おとぎ話をするときのような、ちょっとワクワクするような気持ちで会話をした。<br />
<br />
<br />
8年位前、母の義理の兄が亡くなった。葬儀の帰途、母は実の姉らと共に大きな虹を目撃したそうだ。母は、お義兄さんはあの虹の橋を渡っていったんだね、という話をしたらしい。<br />
この話はその数ヶ月後、母が入院していた病室から虹(写真)が見えたときに語ったものだった。その晩、母は亡くなった。母自身も見たその虹で、誰かがお迎えに来たのだろうか。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixF_GKAKpxKmk-innGJcVSWkGw3XuPXiP7547SLrRw3JRhJE4j5FhWs4iMKEnYmj36RQFSjVSgNo14FmGbagcW7NPgxdzKrPJS5p81cs5hstBwTwitvRrUOQJqiiQBBHZ1pwDt2fWEC47S/s1600/Rainbow%25E9%2583%25A8%25E5%2588%2586%25E8%2589%25B2%25E8%25AA%25BF%25E6%2595%25B4%25E6%25B8%2588.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="513" data-original-width="1600" height="127" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixF_GKAKpxKmk-innGJcVSWkGw3XuPXiP7547SLrRw3JRhJE4j5FhWs4iMKEnYmj36RQFSjVSgNo14FmGbagcW7NPgxdzKrPJS5p81cs5hstBwTwitvRrUOQJqiiQBBHZ1pwDt2fWEC47S/s400/Rainbow%25E9%2583%25A8%25E5%2588%2586%25E8%2589%25B2%25E8%25AA%25BF%25E6%2595%25B4%25E6%25B8%2588.jpg" width="400" /></a></div>
<br />
<br />
僕はこれまで、誰かが亡くなって、その人の家から天国に繋がる雲の道を、他にも2度くらいは目撃したと思う。<br />
<br />
<br />
これらは奇跡だろうか。あるいは、単なる偶然なのだろうか。<br />
<br />
母が亡くなった日に虹が現れたことをとある知人に話したことがある。単なる偶然だと否定した。その知人は当時までは友人だと思っていたが、そうではなかったのかも知れない。医師で人の死が身近すぎてむしろ人の命に鈍感だったのかも知れないし、医療技術や科学は完璧だと奢っていたのかも知れない。<br />
<br />
しかし、偶然であることは奇跡の対義語ではない。その知人は明らかに奇跡を否定する意味で偶然という言葉を用いた。その知人に限らず、奇跡と偶然を対義語として、あるいは、奇跡を否定する意味で使っている人は多い。<br />
<br />
実際は、奇跡と偶然は同質の事実を伝えている。立場や見方によって使われる言葉が違うだけだ。奇遇、という言葉もある。<br />
<br />
変な喩えだが、光が、観測の仕方によって素粒子のように見えたり、あるいは波の様に見えることがあるのと似ている。(見えるといっても、実際に直接肉眼に映るというわけではない。観測により素粒子や波の特徴を科学的に捉えることができるという意味だ)<br />
<br />
では、偶然と必然でははどうだろうか。これも見方の違いではないだろうか。ただし、この場合必然と判断するためには、偶然と見なすよりもより広範囲の視野で注意深く見る必要があるだろう。<br />
<br />
上記の奇跡も、ある種の必然性を訴える言葉でもある。であるならば、視野を広げ、そう見える人の立場や心に寄り添ってもう一度事実を見直してみなければ、その本質を見る事はできないのではないだろうか。偶然という心ない言葉で否定する前に。<br />
<br />
<br />
<h3>
追記:</h3>
<div>
<a name='more'></a><br /></div>
他にも妙な言葉や概念の対比を行う人は多い。芸術 v.s. エロ、はその代表格だ。全く無意味な対比で、エロを表現する芸術は多数あるし、エロでも芸術でもない表現娯楽も多数ある。全く無意味で下らない論争だ。<br />
この論争は、エロな絵画を高尚な美術館に展示するのはけしからん、みたいな文脈で使われることが多い。そして、芸術なら許されると。<br />
しかし、エロティシズムを芸術と言われるまでの高度な技法を使って表現するなら、安っぽいエロよりもずっと性的欲求を喚起する。むしろその意味では危険なのだ。<br />
<br />
<br />T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-74358699349125666462018-01-06T23:39:00.000+09:002019-02-01T11:27:28.980+09:00Cプログラマのための、C++簡単裏道アプローチ(6)<h2>
いつも同じようなデータの入れ物を作っているならSTLが使える</h2>
<div>
裏口アプローチの最後は、データの入れ物(コンテナ)の話です。<br />
<br />
今回も、既にC++を知っている人がCを使わざるを得ない環境で、<br />
「ああC++のこの機能だけでも使えたらな」<br />
と、感じるような内容です。<br />
<br />
配列はC言語にももちろん備わっていますが、固定長です。可変長の配列が必要な場合、realloc()等での操作が必要になるなど、かなり複雑で面倒なことになってしまいます。<br />
<br />
まずはC++での可変長配列の例を見てみましょう。<br />
<int></int><br />
<h4>
コード1</h4>
<pre class="code-sample">void func1()
{
vector<int> numbers = {1,2,3,4}; // 配列の宣言と初期化
// 要素1を200に変更->{1,200,3,4}
numbers[1] = 200;
// 配列の末尾に要素(10)を1つ追加->{1,200,3,4,10}
numbers.push_back(10);
// 要素2の前に300を挿入->{1,200,300,3,4,10}
numbers.insert(numbers.begin() + 2,300);
// 要素4を削除->{1,200,300,3,10)}
numbers.erase(numbers.begin() + 4);
// 表示
for(int i = 0 ; i < numbers.size() ; ++ i)
{
printf("numbers[%d]=%d\n", i, numbers[i]);
}
}</pre>
デモンストレーション的に無意味な操作をしていますが、末尾に要素を追加するのはもちろん、途中への挿入、削除なども行っています。<br />
これらと同じ処理をCで行うことを想像してみれば、比較にならないほど圧倒的であることが容易に判断できますね。<br />
<br />
もう少し、このコードについて説明します。<br />
<br />
関数の最初で vector<int>という型でnumbersという変数(オブジェクト)を宣言し、通常の配列のように初期化しています。ここで使用している<int>の部分を変更すれば、要素にあらゆる型を適用することができます。<br />
<br />
<h4>
コード2</h4>
<pre class="code-sample">struct XY {int x; int y;};
vector<int> intNums = {1,2,3};
vector<double> doubleNums = {1.0, 2.0, 253.9991};
vector<XY> xys = {{1,100}, {2,300}};</pre>
特に注目して欲しいのは、3番目の独自の型を適用できる点です。<br />
<br />
まず、最初に説明しなければならないのは、ここで使用しているvectorというのは単にC++のライブラリであるということです。まるでC++という言語に組み込まれているかのように見えるところもありますが、STLというライブラリの1つです。<br />
<br />
STLは標準テンプレートライブラリ、という名称です。テンプレートはC++の文法の1つで、型そのものをパラメータとしてコーディングすることができる機能です。<br />
<br />
テンプレートを関数に適用した例を示します。max()はCではマクロとして実装できますが、マクロには副作用があるため、通常の関数の方が望ましいわけです。しかし、そのまま関数にしてしまうと仮引数の型指定によって1つの型に決まってしまい、それ以外の型に対応できなくなります。そこでテンプレートを使用します。<br />
<br />
<h4>
コード3</h4>
<pre class="code-sample">// 関数の実装
template <typename T>
T max(T var1 , T var2)
{
if( var1 > var2 )
return var1;
else
return var2;
}
// 関数の使用例1
void func2()
{
int iMax = max<int>( 111, 112 ); // iMax = 112
double dMax = max<double>( 1.2, 1.1 ); // dMax = 1.2
}
// 関数の使用例2
void func3()
{
int iMax = max( 111, 112 ); // iMax = 112
double dMax = max( 1.2, 1.1 ); // dMax = 1.2
}
</pre>
これはテンプレート関数と言いますが、関数の使用例1の基本的な記法を見ると、<typename T>というタイプパラメータにintやdoubleが適用されることが分かりやすいと思います。<br />
使用例2は<int>等の部分を省略した記法で、この場合maxの引数部分の型から自動的に判断されます。<br />
<br />
テンプレート機能を使用した場合、その実装部分ではまだ実際には仮の状態であり、使用部分で型が決まった時点で実装が完結する仕組みになっています。<br />
余談ですが、そのためライブラリと言ってもその一部、または全てがソースコードで提供されることになります。<br />
<br />
<br />
さて、ここまでは比較的シンプルな配列という入れ物を見てきました。C++ライブラリの柔軟性やその可能性について感じていただきたかったからです。<br />
しかし、データの入れ物として、単なる配列だけではその有用性は限られたものに感じられるかも知れません。STLには他にも次のような種類のコンテナが用意されています。<br />
<br />
<ul>
<li>vector:動的な配列</li>
<li>map:キーと値のペアを格納する(キーはユニーク)</li>
<li>multimap:キーと値のペアを格納する(キーは重複可能)</li>
<li>set:値を持たないmapのようなもの(ユニーク)</li>
<li>multiset:multimapの値を持たないようなもの(重複可能)</li>
<li>list:双方向リスト</li>
<li>forward_list:片方向リスト</li>
<li>deque:両端キュー</li>
<li>queue:キュー</li>
<li>priority_queue:優先度付きのキュー</li>
<li>stack:スタック</li>
</ul>
<br />
以上は代表的なもので、他にも多少バリエーションが存在します。また、以前紹介したstringも“コンテナ相当”と位置づけられていて、コンテナ相当のものは他にもいくつか存在します。<br />
<br />
キューやリスト、スタック等はなじみ深いと感じる方も多いかもしれません。毎回、新しい開発の度に少しずつ違う構造体を扱うために同じ機能の似たようなプログラムをよく作っている、という人も多いのではないでしょうか。現実的に、C言語ではそうせざるを得ません。しかし、C++を知っているとそういった労力が無駄に思えてきます。<br />
<br />
もう1つだけ、上記の中からmapを紹介しておきます。これは連想配列を実現するものなのですが、メモリ上に簡易データベースを構築できると考えてもいいものです。ただし、本格的なデータベースに比べれば機能も限定的ですし、パフォーマンスも期待できません。<br />
とは言え、連想配列はその使用方法が非常に明快かつ柔軟性もあり、初めて知ったときは“目から鱗”的な感慨があったと記憶しています。<br />
<br />
<h4>
コード4</h4>
<pre class="code-sample">void func4()
{
map<const char *,double> populations;
populations["東京都"] = 927.3; // 東京都は927.3万人
populations["横浜市"] = 372.5; // 横浜市は372.5万人
populations["北海道"] = 547.4; // 北海道は547.4万人
printf("横浜市の人口は%f万人\n", populations["横浜市"] );
}
</pre>
これは機能の説明はほとんど要らないのではないでしょうか。一目瞭然の動作ですね。<br />
<br />
配列の添え字に数値ではなく、文字列を使用しています。そして、元々なにもないところに値を設定すると、その項目が追加されます。また、既に項目が存在すれば、データは新しいものに置き換えられます。<br />
<br />
もし、項目がヒットしなければ(見つからなければ)値のデフォルト値(この場合doubleなので0.0)が返されます。ヒットしないことを知りたいなら[]による添え字ではなく、組込みのfind()関数を使用すればいいだけです。<br />
<br />
添え字を使った場合、項目がヒットしないとデフォルト値が返されることには意味があります。次のコードを見てください。<br />
<br />
<h4>
コード5</h4>
<pre class="code-sample">void func5(const char * name)
{
static map<const char *,long> nameCounts;
printf("%sは%ld人\n", name, ++ nameCounts[name] );
}
</pre>
nameが初めて出現した場合、longの初期値0が返されるので、インクリメントは期待通り1からカウントされます。<br />
<br />
<h3>
最後に</h3>
</div>
<div>
<a name='more'></a><br /></div>
<div>
ここまで拙い記事を読んで頂き、ありがとうございました。<br />
<br />
今はすっかりオブジェクト指向がネイティブである世代が活躍、台頭している時代と思っていましたが、最近のある経験で案外そうではない人たちも大勢いる、ということが判ってきました。おそらく、彼らは同じ環境にずっといて、彼らにとって新しい世界を知るきっかけを奪われていたのだろうと思われます。(インターネットの規制のある国の人たちだって海外旅行は自由にできるのに、自分たちの国の体制に疑問を持つ人がほとんどいないのと似ている気がしました)<br />
<br />
最初は信じられませんでした。それ、冗談だろう? と思うようなことがあって、自分にとって30年位前に終わっていた議論を、彼らは未だにしていたのです。どうでもいいことや、明らかに世の中が結論を出していること、あるいは論理的に結論していることをほじくり返していたのです。他に考えることがないのかも知れません。当然ながら、彼らの書くコードは洗練とはかけ離れたものに見えました。<br />
<br />
オブジェクト指向を研究すれば、もっと大切で肝となるような議題はいくらでもあります。そして、それはオブジェクト指向設計の醍醐味の1つでもあります。<br />
<br />
しかし、オブジェクト指向というのは、何も知らない人にとっては大きな壁に見える可能性があります。だから、今回はあえてオブジェクト指向をできるだけ避けて、それでもC++によるプログラミングの素晴らしさの一部を知っていただきたく、記事を書きました。C++という言語のパワーの一端をご紹介したつもりです。<br />
<br />
しかしながら、この言語がさらに強力に威力を発揮するのは、やはりオブジェクト指向設計を行ったときです。これまでご紹介してきた内容は、それに比べればおまけ程度のものなのです。<br />
<br />
いずれ、機会を設けてオブジェクト指向プログラミングの裏口入門の記事を書いてみたいと思っています。オブジェクト指向の聖書?のように荒唐無稽でつかみ所のない宗教画、あるいは絵に描いた餅を見ながら学ぶのではなく、目の前にある問題を解決する簡単な応用例を示し、必然性を感じて頂きながら進めていけるものにしたいと思っています。<br />
<br />
それでは、またいずれ。<br />
<br /></div>
T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-11748003641924856852018-01-05T21:06:00.000+09:002019-02-01T11:27:12.368+09:00Cプログラマのための、C++簡単裏道アプローチ(5)<h2>
増殖するエラー処理は例外処理ですっきりと</h2>
<div>
想像してみてください。もしも、エラー処理を書かなくて良かったとしたら。コードはどれだけすっきりし、本来の処理の流れが容易に見渡せるのか。<br />
逆に言えば、コードの多くを占めるのがエラー処理ではないでしょうか。特にC言語の場合はそれが顕著です。<br />
<br />
そのエラー処理とはいったい何なのでしょうか。まずはエラーの検出。多くの場合、関数の戻り値によって判定します。<br />
エラーと判定された場合、何をするのでしょうか。まずはエラーのレベルの判定をして、致命的で処理を続行することができなければプロセスを終了させなければなりません。ユーザインタフェースを起因とするエラーであれば、ユーザーに通知する必要があります。あるいは、通信相手にエラーになったことを通知することもあるでしょう。<br />
そして、共通するのはあとでメンテナンスをするためにログに書き出すことです。<br />
<br />
ログ出力はシステム設計の時点で計画されることが普通で、1カ所にまとめて出力することで処理の分析を容易にすることができます。良くあるのは、エラーレベルという属性を伴うことです。これにより出力時にフィルタリングすることもあれば、ログを解析するときにフィルタリングする場合もあります。よくあるレベル分けには、デバッグ、ウォーニング、エラー、致命的エラー、といった分類で、レベル以外にも種類や系統のフラグ属性を付加することもあるでしょう。大規模なシステムでは、発生すると赤色灯を点灯させるようなものもあります。<br />
<br />
一言でエラー処理と言っても、その内容は多種多様です。そして、システムの保守性、信頼性を高めるためにも決して省略することはできません。<br />
<br />
しかし、です。このエラー処理のために、プログラムコードから本来の処理の流れが読みにくくなってしまうという大きな問題を抱えています。これは仕方がないのでしょうか。<br />
<br />
C++ではCにはなかった例外処理という大胆な仕組みが追加されました。これをうまく使うことで、本来の処理の流れをつかみやすい、良質なコードを書くことが可能になります。<br />
<br />
エラー処理の簡単なパターンを示します。<br />
まずはエラー処理を省いて処理の流れを見て、つぎにCでエラー処理を追加した場合にどうなるのか見てください。<br />
<br />
<h4>
コード1</h4>
<pre class="code-sample">// エラー処理のない仮コード
void func()
{
foo1(); // どこかのライブラリ関数
foo2(); // どこかのライブラリ関数
fooEnd(); // どこかのライブラリ後処理関数
}
void funcRepeat(in n)
{
for(int i = 0 ; i < n ; ++ i)
{
func();
}
}
int main()
{
funcRepeat(10);
return 0;
}
</pre>
<h4>
<br />
</h4>
<h4>
コード2</h4>
<pre class="code-sample">// エラー処理付き、Cの場合
int func()
{
int result1 = foo1(); // どこかのライブラリ関数
if( result1 != FOOLIB_NOERROR ) // libのエラー判定
{
// 関数名やコースコード行数とともにログ出力
logout("foo1() = %d", result1);
fooEnd(); // どこかのライブラリ後処理関数
return MYERROR_FATAL;
}
int result2 = foo2(); // どこかのライブラリ関数
if( result2 != FOOLIB_NOERROR ) // libのエラー判定
{
// 関数名やコースコード行数とともにログ出力
logout("foo2() = %d", result2);
fooEnd(); // どこかのライブラリ後処理関数
return MYERROR_FATAL;
}
fooEnd(); // どこかのライブラリ後処理関数
return NO_MYERROR;
}
int funcRepeat(in n)
{
int myResult = NO_MYERROR;
for(int i = 0 ; i < n ; ++ i)
{
if(func(i) == MYERROR_FATAL)
{
logout("func() %d回目でエラー", i + 1);
myResult = MYERROR_FATAL;
break;
}
}
return myResult;
}
int main()
{
if(funcRepeat(10) != NO_MYERROR)
{
logout("funcRepeat() は失敗した");
return 1;
}
return 0;
}
</pre>
このコードはもっと最適化することは可能かも知れませんが、現実的にはこのようにエラー処理だらけになってしまいがちです。<br />
では、C++の例外処理ではどうなるでしょうか。<br />
<br />
<h4>
コード3</h4>
<pre class="code-sample">// エラー処理付き、C++の場合
void func()
{
try
{
foo1(); // どこかのライブラリ関数(例外発生の可能性あり)
foo2(); // どこかのライブラリ関数(例外発生の可能性あり)
fooEnd(); // どこかのライブラリ後処理関数(例外は発生しない)
}
catch(LibExp &e) // foo1()またはfoo2() の例外
{
fooEnd(); // どこかのライブラリ後処理関数
// 例外情報を使って内容をログ出力
logout("%s:%d", e.errstr, e.code);
throw MyExp("%sエラー発生!",e.errstr); // 独自の例外を発生
}
}
void funcRepeat(in n)
{
for(int i = 0 ; i < n ; ++ i)
{
func(i);
}
}
int main()
{
try
{
funcRepeat(10);
}
catch(MyExp &e) // 独自の例外
{
// 例外情報を使って内容をログ出力
logout("%s", e.errinfo);
return 1;
}
return 0;
}
</pre>
tryとcatch、およびthrowというキーワードが現れました。<br />
try{}の中だけを見ると、コード1のエラー処理がない場合とほぼ同じに見えます。正常処理の流れがCの場合に比べ、よく判るのではないでしょうか。<br />
このtry{}の中で例外が発生し、その例外を捕捉するのがcatchです。例外はthrowで発生させますが、このときパラメータを与えます。そのパラメータは自由な型のものが指定できます(たとえばint型でもいいし、独自のクラスや構造体でも構いません)。catchはその型を指定し、関数の仮引数と同じように例外情報を取得することができます。<br />
<br />
もう一度コード3を見てください。funcRepeat()の中にはtryもcatchもありません。throwで発生させた例外は捕捉されるまで関数コールを遡って伝わっていくので、ここではmain()で捕捉するようにしています。もし、最後まで例外が捕捉されないと、プロセスは異常終了するはずです。<br />
<br />
例外、という言葉は語感として強いものがありますが、どこかで例外が発生したらシステムの処理を続行できなくなる、という意味ではありません。今まで、関数の貴重な戻り値を使ってエラーを通知していたものをそっくり置き換えることが可能です。<br />
たとえば、ファイルのオープンエラーで例外が発生したとすると、再度ユーザにファイル名を入力してもらい、正常処理に戻るといったことも可能です。<br />
<br />
また、今回ここでは示しませんが、例外処理は関数コールを跨がなくても使えます。同じ関数の中でtry、throw、catchがあってもいいのです。そう考えると応用範囲はもっと広がるように感じられますよね。<br />
<div>
<br /></div>
ところで、コード2と3のfuncRepeat()関数を見ておや?と思ったかも知れません。関数コールの途中で何もしなくても良いことを強調するためにこのようにしたのですが、ループの何回目でエラーになったかログを出す必要があれば、次のコード4のようにします。<br />
<br />
<h4>
コード4</h4>
<pre class="code-sample">void funcRepeat(in n)
{
for(int i = 0 ; i < n ; ++ i)
{
try
{
func(i);
}
catch(MyExp &e)
{
logout("func() %d回目でエラー", i + 1);
throw;
}
}
}
</pre>
throwにパラメータを渡していません。catchの中でthrowする際にパラメータを指定しなければ、catchした例外を再度発生させることができます。ここではログ出力だけが目的なので、例外情報にはなにも手を加える必要がなく、このようにしました。<br />
<br />
<br />
さて、ここまで細かいところは気にせず、C++ではこのような例外処理の仕組みを使ってエラー処理を書くことができる、という点だけ押さえておいてください。<br />
例外処理により、エラー処理の冗長なお決まりコードを減らすことが可能です。そのことで本来の処理の流れがつかみやすく、更に、エラー処理自体の流れも判りやすくなり、高品質、かつ、無駄な工数を削減できるコードを書くことが可能になります。<br />
<br />
例外処理を使えるだけでも、C++を使う価値があると言えます。これを知っていながらあえてC言語のみを使ってコードを書かなければならないときに感じる理不尽は、かなり強烈なものがあります。<br />
<br /></div>
T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-9516420413826797502018-01-02T20:17:00.000+09:002019-02-01T11:26:53.191+09:00Cプログラマのための、C++簡単裏道アプローチ(4)<h2>
スコープ</h2>
<div>
C言語だけを使っていると、スコープという言葉はあまり登場しません。これは、変数名や関数名のアクセスできる範囲、見える範囲のことです。</div>
<div>
<br /></div>
<div>
Cの場合、グローバル、ソースファイルローカル、関数ローカル、の主に3種類しか存在しません。これらはプログラミングのため、というよりも、半ば古いコンパイラの都合に合わせた仕様のように感じられます。</div>
<div>
<br /></div>
<div>
これらの選択肢で、グローバル変数の使用は通常例外的なものです。場合によってはコーディング規約でグローバル変数の使用を禁止するところもあるでしょう。理由は説明するまでもないことですが、いつ、どこからアクセスされているか判らないからです。それが判らないとコードの保守性が著しく悪化し、バグ対応で無駄に多くの時間を奪われることになりますし、機能追加や変更時にもソースコードの解析に無駄に労力を割くことになってしまいます。</div>
<div>
コーディング規約といえば、goto文を使用しないように指導しているところも多いですが、多重ループからの抜け出し等、戻りジャンプをしない約束で例外的に使用することは効果的であり、威力を発揮することがあります。同様に、グローバル変数もシステム設定値などでの使用を例外的認めることで効果的、効率的なプログラミングが可能になります。</div>
<div>
<br /></div>
<div>
構造化プログラミングができるC言語は、関数ローカルな変数があります。自動(auto)変数でも静的(static)変数でもスコープは一緒です(ただし、生存期間は異なります)。関数のパラメータも自動変数の仲間ですね。</div>
<div>
<br /></div>
<div>
せっかく、グローバルとローカルという区別があってそれがヒントになっているはずなのに、関数内グローバル変数といって、関数の先頭にまとめてローカル変数を宣言する人が未だに存在します。<br />
<br /></div>
<div>
そもそも、関数の先頭にまとめて変数を定義するのは、大昔のコンパイラの制限でした。何かの処理の後に変数を定義するとコンパイルエラーになりました。つまり、プログラミングの正当性(ソフトウエア工学)とはなんの関係もありません。なので、その制限はとうの昔に取り払われています。</div>
<div>
<br /></div>
<div>
変数というものは、使わないで済むなら使わない方がいいのです。必要悪と言ってもいいでしょう。もしどうしても使うのであれば、できるだけ小さな範囲に限定して使うべきです。そうすることで、いつ何処でアクセスされているかを調べることがより簡単になり、コードの保守性が高くなるからです。これに関しては疑う余地はありません。もし、これを否定するのであれば、<span style="color: #741b47;">全てグローバル変数で書けばいいだけの話です。もはやローカル変数さえ必要ないでしょう。どうぞ、暗黒時代に逆戻りしてください。</span></div>
<div>
<br /></div>
<div>
変数は必要になった時点で宣言すればいいのです。そうすれば、それ以前にどう使われているか心配しなくていいからです。<br />
<br />
しかし、残念ながら一度定義した変数を定義前の状態に戻すことはできません。また、僕はまだそれを行える言語を知りません。それに近いことは、CやC++なら中括弧(この括弧のこと→{})で囲めば実現できますが、これはこの中に定義した変数全てが対象になるので本質的ではありません。</div>
<div>
<br /></div>
<h4>
コード1</h4>
<pre class="code-sample">void func1()
{
int v1 = 1;
{ // スコープのためだけの中括弧
int v2 = 2;
int v3 = 3;
printf("v1 = %d, v2 = %d, v3 = %d 合計は %d\n", v1, v2, v3, v1 + v2 + v3);
// 以下、v3は不要だが、スコープに含まれる
printf("小計 = %d\n", v1 + v2 );
}
// v2とv3はスコープを外れるのでコンパイルエラー
printf("合計 = %d\n", v1 + v2 + v3);
}
// こんなことができたらいいかも
void func2()
{
int v1 = 1;
int v2 = 2;
int v3 = 3;
printf("v1 = %d, v2 = %d, v3 = %d 合計は %d\n", v1, v2, v3, v1 + v2 + v3);
undef v3; // 以下不要なので変数を捨てる
printf("小計 = %d\n", v1 + v2 );
// v3はここでは存在しないのでコンパイルエラー
printf("v3 = %d\n", v3);
}
</pre>
<div>
func1()では中括弧を変数のスコープを制限するためだけに使用しています。文法上は問題ないのですが、違和感があり、つい、中括弧の先頭にifやwhile等を探してしまいます。ややこしいので僕はこの方法を使うことはほぼありません。たまたまifやループの処理部分などで中括弧で囲まれた部分があれば、積極的にスコープの制限を利用します。</div>
<div>
<br /></div>
<div>
func2()は架空の文法です。中括弧に頼らず、変数をいつでも廃棄できれば、それより後のコードで間違ってアクセスされてしまう心配がなくなります。その際、同じ変数名をそれ以降に再定義できるかどうかは議論の余地はありそうです(CやC++に追加するなら、できるようにするのでしょうね。中括弧スコープでもそれができますから)。<br />
<br />
forループによく使うカウンタ変数「i」。これも、たいていはそのループの中だけで使用するので、大昔はできなかったのですが、forの小括弧の中で定義できるようになりました。</div>
<br />
<h4>
コード2</h4>
<pre class="code-sample">void func3()
{
char name1[] = "suzuki";
char mail[] = "ABC00123";
// 大昔の書き方
int i;
for(i = 0 ; name[i] ; ++ i)
{
putchar(name[i]);
}
for(i = 0 ; mail[i] ; ++ i)
{
putchar(mail[i]);
}
}
void func4()
{
char name1[] = "suzuki";
char email[] = "suzuki@abcnet.x";
// 現代の普通の書き方
for(int i = 0 ; name[i] ; ++ i)
{
putchar(name[i]);
}
for(int i = 0 ; email[i] ; ++ i)
{
putchar(email[i]);
}
}
</pre>
<div>
今の普通の書き方であれば変数の使い回しをせずに済みます。また、なによりforループは変数の定義を含めて構文の一部なので、それをわざわざ外部の変数をループカウンタに使用する意味、意義はまったくありません。<br />
<br />
ところで、コード2のfunc4()で、文字列の長さを同時にカウントしたい場合はどうでしょうか。forを抜けたときにはもうiは使えません(MicrosoftのVisual C++では以前、独自拡張でスコープが中括弧外に及んでいた黒歴史があります)。func3()ならforを抜けた直後、iに文字列の長さが結果的に記録されています。<br />
<br />
forループに入る前にカウント用の変数をもう1つ作って、ループの中でそちらにiの値を毎回コピーするか、iと同じ場所でインクリメントする。<br />
あるいは、例外的にfunc3()のような書き方をする。ただし、単なるiという名称は避けて、たとえばcounterといった変数名にする。<br />
僕は後者を選びます。前者は変数の使い方に、より厳密性を持たせてはいますが(1つの変数に複数の意味を持たせない)、変数が増え、また実行コードも増えてしまいます。また保守性もむしろ良くはありません。<br />
基本的にはfunc4()の書き方で、例外的に変数を外に出しているのだから、コードを読む人はそれがforを抜けてからも必要になるだろうことがすぐに理解できるでしょう。<br />
<br />
もちろん、もっと判りやすいコードに徹すれば、1つの処理に複数の機能を持たせず、文字列長は素直にstrlen()を別途使って調べるというのも現実的には大いに意味のある選択肢です。昔のCPUパワーを含めた貧弱なリソースの環境では避けたくなるところでしたが、今時のハードウェア環境でそれが問題になることは滅多にありません(この話をすると、1文字ずつのputchar()の意義について考えなくてはいけなくなりますが、これは必然性のある処理だと仮定しての話ということでご勘弁を・・・)。<br />
<br />
<br />
さて、ここまで長くなりましたが、C++でのスコープはどうなっているでしょうか。<br />
<br />
まず、ライブラリなどの関数名などについての管理では、グローバルとかファイルローカルという区別だけではなく、namespaceというキーワードでグループ化して管理する機能があります。<br />
<br />
C++ではCに比べてより広大なライブラリが標準化されるなどしています。Cのように全てが同じ名前空間にあっては、独自の関数名や変数名、クラス名(構造体のようなもの)がバッティングしてしまう確率も高くなってしまいます。<br />
それを避けるために名前空間を分ける機能が必要なのです。入れ子にして、ファイルシステムのディレクトリ(フォルダ)ツリーのような管理が可能です。<br />
<br />
また、C++では通常関数を中心にしたプログラミングをするわけではなく、クラスというものを主体に考えます。オブジェクト指向が絡んでくる話なので詳しくは説明しませんが、Cでは「何々をする」というプログラミングしかしないのですが、通常C++では「何が何々をする」というプログラミングになります。最初に、「何が」の部分がオブジェクト指向でのオブジェクトで、関数はそのオブジェクトに所属するわけです。<br />
そのオブジェクトを定義するのがクラスです。ややこしいですね。<br />
クラスは構造体だと思ってください。構造体のメンバーに関数が定義できると思っていただければいいのです。Cの構造体でも、やろうと思えば構造体のメンバーに関数ポインタ変数を定義することは可能です。C++ではそれをポインタではなく、実際の関数を定義することができるのです。<br />
<br />
なぜそんなことをするかはここでは説明しませんが、その構造体のメンバである関数を構造体の外から呼出すことが、当然ながら可能です。</div>
<br />
<h4>
コード3</h4>
<pre class="code-sample">// 構造体の定義
struct A // C++ではtypedefが不要
{
void func1(){ puts("私はA::func1()"); }
};
// 呼出
void main()
{
A a;
a.func1(); // "私はA::func1()"
}
</pre>
<div>
でも、メンバ関数の全てを外部の誰からでも呼ばれては困ることもあります。ちょうど、C言語でもファイル内部からだけ呼ばれることを許可するために static を使用して制限するのと似ています。<br />
そこで、プライベートな関数にはprivateというキーワードを使います。</div>
<br />
<h4>
コード4</h4>
<br />
<pre class="code-sample">// 構造体の定義
struct B
{
private:
void func1(){ puts("B::func1()"); }
public:
void func2()
{
printf("私はB::func2()&");
func1();
}
}
// 呼出
void main()
{
B b;
b.func2(); // "私はB::func2()&B::func1()"
b.func1(); // コンパイルエラー
}
</pre>
<br />
<div>
構造体の private: 以後は全てプライベートになってしまうので、再度外部からアクセスできるようにその後 public: を使用しています。<br />
B::func1()はメンバ関数からのみアクセスが可能で、外部からアクセスしようとするとコンパイルエラーになります。<br />
これは関数だけではなく、メンバ変数についても同様です。構造体のメンバ関数からだけアクセスできる変数が定義できるわけです。<br />
<br />
このように、C++では隠蔽という概念が反映されていて、アクセス制御という概念を積極的に取り入れています。ここで紹介したこと以外にも、更に信頼性や保守性を高めるための機能が実装されています(若干、実行時のパフォーマンスを犠牲にしてでも安全性や保守性を採る仕組みも含まれます)。<br />
<br />
<br />
今回は少々雑然とした説明に終始してしまいましたが、悪用されないように必要最小限に名前は公開する、という考え方がより強力に実現できる機能がC++には実装されている、ということが解っていただければと思います。<br />
<br />
そして、普段Cを使っていて、必要以上に名前が公開されてしまうことに不満を感じることはとても重要なセンスだということです。不満を我慢しているうちに忘れてしまったりしないで、自分なりの工夫をすることが大切です。<br />
僕はC++に出会ったとき、そういった不満が解消されることが痛快でもありました。</div>
<br />
<br />
<a name='more'></a><h3>
コラム:変数の使い回し</h3>
<div>
<br />
僕自身も初心者の頃には良くやっていました。関数の中で、例えば work という名前の変数を使用することです。これはその中身に何を入れてもいい変数ですから、1つだけならまだしも、work1、work2等と増えていくと、これはソースコードを暗号化することに等しい行為になってしまいます。<br />
<br />
もちろん、僕が初心者の頃というのはMS-DOSの16bitメモリ空間、極端に制約されたリソース環境でしたから、変数の使い回しもある程度仕方がない面もありました。<br />
<br />
しかし、現在のコンピュータは当時とは比較にならないくらい広大で潤沢なリソース環境である場合がほとんどです(一部の組込みなど、特殊な環境でない限り)。なので、リソースの節約よりもコーディングの効率、安全性、保守性により重点を置くことが求められます。<br />
<br />
そういった環境での変数の使い回しはごく一部の例外に限らなければなりません。<br />
<br />
以前、機能追加の仕事で今から10年位前にプログラミングされたコード解析する必要がありました。その実際の処理内容は実にシンプルであるにもかかわらず、通常の10倍程度の時間を要してしまいました。原因は変数の使い回し。同じ変数をいろんな目的に使っていたのですが、更に酷いのは、最終的に格納する領域さえも、一旦それとは全く関係のない内容を格納し、編集の一時領域に使用していたりしたことです(アセンブラのレジストリ操作のようですね)。本当に、暗号化されたプログラムの解析をしているような気分になりました。<br />
<br />
あまりに酷いので、その部分は全て書き直してしまいました。そのコードの一部に手を入れるなど、不可能に近かったからです。作った人は初心者に近い人だったに違いありませんが、それにしても酷すぎました。それまで良く動いていたと思います。<br />
<br />
<br /></div>
T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0tag:blogger.com,1999:blog-762781153744850354.post-42364659781541900252017-12-28T22:40:00.000+09:002019-02-01T11:26:10.529+09:00Cプログラマのための、C++簡単裏道アプローチ(3)<h2>
とても便利なC++の文字列型</h2>
<div>
Cで制御系の処理を書く場合、確かに文字列そのものをほとんど扱わないことも珍しくありません。しかし、そういった場合でもログ出力だったり、デバッグのための情報記録まで含めて考えると、ほとんどの場合で文字列を扱わないということはむしろ珍しいケースではないでしょうか。</div>
<div>
<br /></div>
<div>
ところで、C言語には文字列が文法上存在していない、という言い方がされてきました。確かに、char * だったり、const char * だったりと、文字の配列として扱います。しかしながら、リテラル表現はダブルクォートを使用し、その配列の最後はなからず0で埋められるという規則があります。これは文法上の規則です。</div>
<div>
このことを考えると、中途半端かも知れませんが、やはりC言語にも文字列型は存在している、と言っていいのかも知れません。</div>
<div>
(昔々、「"abc\0"」と、わざわざ0を追加している初心者のコードを見たことがありましたが、きちんと文法書を読んでください、と言いたくなりました。初心者と言っても学生の演習ではなく、お仕事のプログラムです。もちろん、0を2つ重ねる目的でもありません。)</div>
<div>
<br /></div>
<div>
この文字列リテラルはC++においても相変わらず有効で、これ自体はよく登場します。</div>
<div>
しかし、C++には標準ライブラリのSTLに文字列クラス(文字列型)が用意されていて、大変便利に、しかも安全に文字列処理を行うことが可能になっています。</div>
<br />
<div>
さて、こういった文字列型になれていると、ちょっとした文字列操作も大変面倒に感じてしまいます。たとえば、文字列の結合。</div>
<div>
<br /></div>
<h4>
コード1</h4>
<pre class="code-sample">// Cの場合
void funcC1()
{
char s1[] = "abc";
char s2[] = "DEF";
char * s3 = (char *)malloc( strlen(s1) + strlen(s2) + 1 );
strcpy( s3, s1 );
strcat( s3, s2 );
puts(s3); // "abcDEF";
free(s3);
}
// C++の場合
void funcCPP1()
{
string s1 = "abc";
string s2 = "DEF";
string s3 = s1 + s2;
puts(s3.c_str()); // "abcDEF";
}
// あるいは
void funcCPP1_2()
{
string s1 = "abc";
string s2 = "DEF";
s1 += s2;
puts(s1.c_str()); // "abcDEF";
}
</pre>
<br />
<div>
stringというのはCでいうところの構造体みたいなもの(クラス)ですが、C++では演算子のオーバーライドという機能(演算子の動作を再定義する機能)があるので、あたかも言語組込みの型のように演算子を使うことができます。そして、Cでやっているようなメモリ確保等の面倒も、全部string型の内部に定義された関数で処理されますから、使うときはそういったことは考えなくてもいいのです。<br />
<br />
ところで、このコードの中で s1.c_str() という関数の呼出があります。C++の場合、Cの構造体に相当するもの(クラス)に変数だけではなく、関数を定義することができます。メンバ関数とか、メソッド、といいますが、Cの関数と似たようなものだと考えてください。<br />
構造体のメンバにアクセスするとき、「.」(ドット)で構造体の実体名(変数名)とメンバ変数を繋ぎますが、メンバ関数も同様で、s1.c_str() と書いて呼出します。<br />
で、この s1.c_str() は s1 の内部に持っている文字列を const char * に変換して返すので、puts()のパラメータに使えるわけですね。<br />
<br />
文字列型が存在することだけでも、プログラミングはずいぶんと違ったものになります。ここでご紹介したのは氷山の一角に過ぎません。C++の新しい仕様では正規表現の処理が標準関数で扱えるようになりました。これは特に文字列の解析処理においてはかなり威力を発揮します。<br />
C言語では似たような発明を毎回しなければならず、問題の本質になかなか迫ることができないもどかしさを感じてしまいます。<br />
<br />
いえ、ここまでの記事だけでは、とうていそれを読者に実感してもらうことはできないとは思いますが。<br />
<br />
簡単ですが、今回はここまでにします。<br />
<br />
<br />
<a name='more'></a><h3>
コラム:動的配列確保</h3>
<br />
コード1のfuncC1()ではわざわざmalloc()を使っていましたが、動的な配列の確保を使えば不要になります。ただし、どうやらこの動的な配列の確保についてはCの文法的に将来性がないかも知れません。また、C++では使えないことになっています(C++では他の方法があるのでそもそも不要と言えます)。</div>
<br />
<h4>
コード2</h4>
<pre class="code-sample">void funcC2()
{
char s1[] = "abc";
char s2[] = "DEF";
// 動的配列確保、自動変数なので開放不要
char s3[strlen(s1) + strlen(s2) + 1];
strcpy( s3, s1 );
strcat( s3, s2 );
puts(s3); // "abcDEF";
}
</pre>
<div>
ご参考まで。</div>
<br />
<br />
<br />
<br />T.2940http://www.blogger.com/profile/13849343480278010467noreply@blogger.com0