aoitaku Advent Calendar 2016 : 2日目 - C++

この記事は aoitaku Advent Calendar 2016 の 2 日目の記事です。「日付またいでても朝が来るまでは当日」と供述しており(もう 4 日なんだよなあ)。

Perl の次の話をします。タイトルに書いてあるんだけどね。

プログラマになった日

今から 9 年くらい前に「職業:プログラマ」になりました。正確に言うと設計もやるので SE でもあったわけですが、アマチュアプログラマでなくプログラムを書いて給料をもらう立場になった、という意味で、ひとつのターニングポイントでした。

就職して出社初日、さっそく現場に放り込まれました。

いや何が起こってるのかぜんぜんわからないんだけど就職したその日にいきなり現場に放り込まれました。OJT? そんなものはなかった。

現場のことをあまり詳しく書くことはしませんが、携帯電話向け組み込みアプリケーション開発をするところに外注スタッフとして配属されました。
組み込みといっても組み込み系ではなくて OS があって OS の上で動くリッチアプリケーションの開発だったので実際 Windows アプリケーション開発とかいまのスマートフォンアプリケーション開発とそうは変わりませんし、というか、そもそもその携帯電話に載っていた OS はスマートフォン向けの OS なわけで、実際のところ当時の携帯電話は構造としてはスマートフォンとほとんど変わらなかったと思っています。

この OS について詳しく書くとぼくが具体的にどの会社のどういう事業で仕事をしていたかわかってしまうので仔細は省略します。大人の事情があるのでこれ以上は書けません。そういえば全然話は関係ないんですけどアドベントカレンダーが話題になっていますね。陰ながら応援しています。

型のある世界

さて、C++ の話をします。ぼくはそれまで Perl を書いていたので、型システムのある言語に触れるのはこれがはじめてでした。もちろん事前コンパイルをする言語もこれがはじめてです。

型という概念、実は Perl でも意識します。

たとえば Perl の比較演算子には二種類あります。ひとつは数値比較演算子。もうひとつが文字列比較演算子です。Perl では数値と文字列は明確に区別されます。数値比較演算子オペランドは数値として扱われ、文字列比較演算子オペランドは文字列かそうでないかも判定します。

1 == '1' # true
1 eq '1' # false

こういう世界にいたので、型らしきものの存在をぼくは知っていました。 むしろ型があることで「ある変数が何を示しているのかは宣言を見ればわかる」という恩恵を受けられることに気付きます。
もちろん型があって困ったこともあります。たとえば配列は基本的に同じ型しか取り扱わないので、Perl のように型を気にせず適当に配列に突っ込むみたいなことはできないし、Perl で最高だと思っていた連想配列のようなものは C++ には基本的には存在しないので、ちょっとしたものをひとつの変数にまとめて取り回したいとなったら構造体をいちいち宣言しないといけなくて面倒だなと思ったりはしました。

ポインタのある世界

C や C++ といえばポインタ。メモリのアドレスがむき出しの世界です。

さて、Perl にはリファレンスがあります。

C や C++ のポインタと Perl のリファレンスの違いは何かというと、やりたいこととしては同じです。違ってるのはメモリのアドレスがむき出しかどうかという点です。

どちらも値そのものではなく値のある場所を指し示す何かという情報を取り扱うという意味では同じものだと思っています。

「ポインタむずかしい」ということをよく聞きます。ぼくは Perl のリファレンスよりポインタのほうがメモリアドレスがむき出しになってる分理解しやすい気がしています。ポインタの場合、変数に入ってるのはメモリアドレスっていう実際の値ですからね。

「ポインタむずかしい」という人は、じゃあたとえば C++ の参照はわかるかというと、たぶんポインタよりむずかしいのではないかと思います。なんなら「ポインタがあるのになんで参照が必要なんだ?」と思うかもしれない。

C++ の参照がむずかしいなら、他の言語の参照だって多分むずかしいと思います。

逆に「変数が何を示しているのか」という根本的な理解があれば、ポインタも参照も概念としてはわかるんじゃないかと思っています。

Perlスクリプト言語ながら、変数の前に記号つけて変数をリファレンスにしたり、リファレンスの記号を置き換えることでリファレンスが指し示している値を参照したりということをします。これはポインタとやりたいこととしては一緒です。

メモリに対する意識

たとえば文字列なんですが、文字列というのは実体は文字の配列です。一文字の実体は何らかの数値で、文字列は文字をあらわす数値の配列だということになります。*1
さて、10 万文字の文字列があったとして、文字列を引数として受け取る関数に、この 10 万文字の文字列を引数に渡すことを考えます。

10 万文字の文字列は関数に渡されるときに、あたらしい変数としてコピーされます。このとき 10 万文字分のメモリコピーが発生します。10 万文字というのは乱暴にいって 2 ~ 300 KB くらいです。*2 元の文字列が 300 KB メモリを使ってて、関数の引数に渡すとさらに 300 KB メモリを使うというわけです。メモリ消費自体もそうですが、300 KB 分のメモリコピーを繰り返すとそれ自体に時間がかかりそうですね? CPU が一度に処理できるデータ量には限界があるので、大きなデータを処理するのはそれだけで時間がかかります。単なるコピーですらそうです。
メモリコピーに時間がかかるのもそうだし、メモリサイズには限界があるので、直接値を扱うことを繰り返しているとメモリが足りなくなったりもします。実際にメモリが限られた環境では関数の呼び出しが深くなるとスタック領域を破壊したりします。

実際に仕事でもスタック破壊に遭遇したことがあります。
開発機は PC なのでメモリは充分にある一方、実機は携帯電話なのでメモリには限りがあり、開発機のエミュレータ上で動作確認する分には問題なく動くのに、実機上では存在するはずの関数の呼び出しでアクセス違反が発生して異常終了する、ということがありました。アクセス違反になるのはスタック領域が足りなくなったからなんですが、スタック領域が足りなくなった原因は何かというと巨大な構造体の値渡しでした。
思わず「なんでこういうことするの……」って口からこぼれましたが、いま思い返してみると原因がわかってよかったですね(よくない)。

ちなみに巨大な構造体はヒープ領域に置くようにしたので問題はなくなりました。

ヒープ領域とスタック領域のことは説明しません。ただ、メモリを意識しないで適当にコードを書くとめちゃくちゃ困るぞということです。メモリを意識するということは、変数が何を示しているのか理解するということだと思っています。

「ポインタを必要以上に恐れるべきではない。ポインタより恐ろしいのはメモリに対する無意識である」

ちなみに、ぼくがメモリを意識するようになったのは Perl のリファレンスについての説明を読んでからです。最初に触れた言語が Perl だったから、ぼくはプログラマとしてなんとかやってこれたような気がします。

オブジェクト指向

Perlオブジェクト指向ではない、とみなされていたように思います。実際には Perl のモジュールはオブジェクト指向をサポートします。C++ に出会う前にぼくはオブジェクト指向について既に知っていました。C++ で class を見て、Perl よりずいぶん簡単にクラスを書けるなという感想を持ったのを覚えています。

しかしめちゃくちゃ覚えることが多かった。 たとえば C++ には interface というキーワードがなくて、interface っぽいものを作るには、純粋仮想関数とかいうやつを使います。ところでいまこのくだり書いてて自分でびっくりしたんだけど C++ なんてもう久しく書いてないのに「 interface のために純粋仮想関数を書く」ということを覚えていてめっちゃびっくりした*3
純粋仮想関数って聞いて直感的にこれが interface のためのものですよってわかるかといったらわかるわけないですし、virtual キーワードが派生クラスでオーバーライド可能だということを示すものですよということもぜんぜんわからなくて、このへんは Java のキーワードの明確さがうらやましく感じたりしました。*4

ジェネリクス

でも一番苦労したのはテンプレートです。ジェネリクスという概念がそもそも未知のものでした。
未知のものだった以上に、テンプレートが絡んだエラーがめちゃくちゃわかりにくくて、どこをどう修正したらエラーを解消できるのかぜんぜんわからなくて苦労した覚えがあります。
ジェネリクスがとても便利なものだということを知るのはもうちょっと後のことですが、当時から今にいたるまでずっとテンプレートに対して「C++こわい」という印象を持っています。

現場から去るとき

この現場には丸三年いました。
あとで聞いた話によると、ぼくが配属された部署は、いろんな外注社員が放り込まれるけどだいたいみんな一ヶ月で異動願を申し出るか、使えないからもう来なくていいよとお払い箱にされるかどちらからしいです。
ぼくよりちょっと前に入った人がものをめちゃくちゃ覚えるのが苦手な人で、ぼくも何度も繰り返し同じことを説明した記憶がありますが、彼はそれでも二年くらいぼくと同じ部署にいました。
彼ですらそれだけ長くいたことを考えると一ヶ月でみんなやめることになったというのは嘘なんじゃないかという気がしていますが、どうも嘘ではないらしく、ほかのアウトソーシング系の会社の人と話したときに「あの現場ねー有名だよねー、どれくらいいたの?」って聞かれて「三年ですかね」って答えたら驚かれたのでこっちが驚いたというかなんというか。

やめた理由は直接的には病気を患ったからです。
まあ慢性的な睡眠不足とか偏った栄養とかストレスとかいろいろあると思いますが、不眠がひどいし何か食べるとだいたい下痢するし偏頭痛は常態化しているし……まあでもわりとある話です。ありふれた話です。

やめずにあの場所にいたら、ぼくはどういうプログラマになっていたのか、たまに想像することがあります。携帯電話の開発は、もう当時そろそろ行き着くところまで来たなという感じで、スマートフォンによって市場は一新されるだろうな、そういう気配がありました。これからは AndroidiPhone の時代になる。それは多分あの場にいた誰もが感じていただろうと思います。
Android 向けの組み込みアプリケーション開発チームに入ることはできなかったんじゃないかな。即戦力になる Java プログラマでチームが再編され、ぼくは携帯電話開発から遅かれ早かれ去っていただろうと思います。次にどこに行ったかはわかりません。
現場を去って、次の現場としてトヨタ系のロボット制御システム開発の現場の面接にいってここは内定出てた*5らしいんですが、ぼくはこれを断って会社を辞めて、しばらくの後、ウェブエンジニアを目指すことになります。

さて、この現場を去るちょっと前くらい、ぼくは RPG ツクール VX を買いました。たぶんらんだむダンジョンをプレイしたからだと思うんですが、現場がわりとしんどくて、手慰みにゲームでも作りたいなとかそういうことを思っていました。 というわけで、C++ の次は…… PHP の話をします。お楽しみに。

*1:適当なテキストファイルをバイナリエディタで開いてみればわかると思います

*2:文字コードによります

*3:書いてからあってるか不安になって一応調べました

*4:今覚えば気の迷いです

*5:面談で話した前の現場での三年間の話が生きたらしいです