気まぐれメモランダム / でたらめフィードバック

ロートルプログラマのC++再訪記

公開:

あんなに親しかったC++と疎遠になって早や十数年。次にネイティブのプログラムを作るならRustを使いたいなあと思っていたところ、何の因果かふたたびC++と相まみえることになりました。疎遠になっていたとはいえ噂話は耳にする間柄、いまどきのC++(いわゆるModern C++)がかつてとは異なる様相を呈していることくらいまでは把握していましたが、細部に目を凝らしたりまでは当然しておらず、何がどうなっているかは浦島太郎状態。しかしC++を舐めてかかると痛い目にあうことは過去の経験で叩き込まれています。これはまずいと必死になってキャッチアップ、なんとか一息付くところまでたどり着いたので、せっかくですからまとめておきます。

TL;DR

  • 関係各位の弛まぬ努力によりModern C++は後発の言語に勝るとも劣らない機能・仕様を維持し続けている
  • しかしC++プログラミング最大のリスクであるメモリ管理関連には抜本的な変更なし、引き続き細心の注意を払う必要あり
  • 選択の幅が広がった現在他の言語と比較するとファーストチョイスとする強みには欠けるが諸事情勘案したうえでのセカンドチョイスとしてならいまだ現実的

リソース

キャッチアップをはじめてまず痛感したのが日本語リソースの少なさ。書籍ではMeyers『Effective Modern C++』は真っ先に入手しましたが、その次の選択肢がいまや本当にない(古本屋でSutter, Alexandrescu『C++ Coding Standards』を入手できたのは僥倖)。Sutter『Exceptional C++』とか手放すんじゃなかったと後悔しても後の祭りです。C++のプログラミングにはバッドノウハウとベストプラクティスが不可欠で、それらがいまだ必要であることは今回あらためて痛感しましたが、現役C++プログラマはそれらをどのようにして身に着けているのでしょうか。他人事ながら心配になります。いまさらながらC++関連書籍の邦訳に積極的だったピアソン・エデュケーションの出版事業撤退が惜しまれます。

Webの情報量も他のメジャーな言語と比較すると豊富とは言えません。Webでの情報発信に積極的な層の関心が高いとは思えませんからそれ自体はまあ順当な結果なのでしょうが、心許なさは否めないところ。そんな中でもcpprefjp - C++日本語リファレンスMicrosoft LearnのC++関連ページは頼りになりました。どちらも主は仕様寄りの情報なので、あとはバッドノウハウ・ベストプラクティスへのアクセスパスがあるとよいのですが……

ビルドシステム・メタビルドシステム

今回は統合開発環境を使わない開発となったので最初はmakefileを書くことになるの?とか思ったのですが、調べてみると迷うくらいには選択肢がありました。更にはメタビルド(ビルド管理)システムなるものまで登場していて、中央集権的なリポジトリは存在しえないにもかかわらず対応する別プロジェクト / ライブラリが参照可能になっているといううれしい誤算も。もちろんエコシステムはモダンな他言語とは比較にならない貧弱さですが、以前よりもハードルが下がっているのはまちがいありません。

とはいえ何を選んだらいいのかわからん感はちょっとありますね。今回は日本語情報へのアクセスしやすさを重視してcmakeを選択しました。

開発環境

エディタはもうVisual Studio Codeがあればたいていのことはなんとかなるという感じ。デバッグ実行もできるしフォーマットも可能、ランゲージサーバーも機能するとなると、足りないものはすぐには思いつきません。もっとも私はコマンドプロンプトでがしがし実行するタイプの旧世代なので、GUI世代の方たちとは感覚が違うかもしれません。

ランゲージサーバーは編集中も機能するので一度保存しないといけないPythonのそれ(私の設定が悪いだけかもしれませんが)よりも開発者体験はよかったです……遅いですけどね。裏で一生懸命パースしてるんだろうなあ。

リンターは現状ではcpplint一択のように思えますが、ちょっと意図のわからないルールがあったりCスタイルのヘッダーを誤判定したりといまいちフィットしない感があります。更新が停滞気味に思える点も気がかり。C++はコンパイラがカバーする範囲が広いので他の言語よりは重要性は低いとは言えますが……

言語仕様・標準ライブラリ

さて肝心の言語仕様や標準ライブラリについて。

キャッチアップでまず感じたのはなによりも関係各位の弛まぬ努力です。C++03からC++11への仕様改訂は言うに及ばず、仕様改訂の定期化で他言語の特徴への早期追従を可能にすることによってC++は現在も一線級の機能を維持し続けています。C++98策定までの長い道のりを思い返せばこのスピード感は驚異的で、そのために費やされた努力は想像を絶します。場末のブログではありますが、ここであらためて最大限の感謝の意を表したいと思います。

ただしそのスピードに追従し続けるのが容易でないことも想像に難くありません。仕様の解説と実際の挙動との乖離を感じる場面はときおりありました(svリテラルの取り扱いなど)。実装やライブラリが更新されるに従い解消されていくでしょうが、あたらしい仕様が定期的に追加されるとなると、差が完全に埋まることはまずありえません。利用者としては実装をたしかめつつ利用するのが現実的な態度になるでしょう(参照実装が提供されるわけではありませんから、これでよいのだと思います)。

個別の要素について触れると、まずはなんと言っても std::variant。継承を主体としたオブジェクト指向に慣れてしまうとなかなかわかりづらい複数の型を継承に頼らずにまとめて取り扱える便利さがC++でも享受できるのはありがたいかぎり。実装でも活用しました。

Rust風に正常値とエラー値がまとめて取りあつかえるstd::optionalも役に立ちました。エラー値の取り扱いが更に容易になるstd::expectedの標準化がいまから待ち遠しいです。とはいえ言語仕様や標準ライブラリはいまだ例外機構と密接に関連づいているため、例外フリーのコーディングが可能になるのはまだまだ先になりそうです。

エラー・例外といえば意外だったのが有名無実化していた throw() (動的例外仕様)が一部 noexcept と名前を変えて復活していたこと。動的例外仕様はその存在を知ったとき有効に活用されればより頑強なプログラムが作れるようになるのにと思ったものですが、影響を受けたJava(検査例外)での利用のされかたを見ればわかるとおり現実はそう甘くありませんでした。結果例外を送出しないという意味でのみ生き残ったのは皮肉ではあります。

overridefinalなど、あってしかるべきものが用意されたのももちろん大歓迎。並行プログラミングも標準でいちおう可能になり、ラムダ式で他言語ほどではないものの簡潔な記述もできるようになりました。不得手な点はあるとはいえ、機能的な面での表現力はほぼ問題ないように思います。

ただし全般的に記述がどうしても冗長になるのはなんとも悩ましいところ。冗長化はもともとはC言語の反省からC++98でではじまっているものなので全否定する気はもちろんないのですが、それにしても……とはやはり思わざるをえません。Google C++スタイルガイドがインデントをスペース2文字にしているのもそのためですよねえ、たぶん。

またunique_ptrshared_ptrの導入によっていささかましになったとはいえメモリ管理まわりはあいかわらずコーディングする側の細心の注意が必要。このコストを上回るだけのメリットが最新のC++にあるか?と問われると考えこまざるをえません。

加えて言語仕様やコンパイラに関係する開発者体験はあいかわらずきわめて劣悪。宣言と定義の分離のためにかかる手間は言うに及ばず、ひたすら長大で原因個所の特定が困難なコンパイルエラーは修正の意欲を著しく削ぎます。この点はテンプレートの活用がさらに進んだModern C++のほうが悪化しているかも。進行中の標準化作業では契約プログラミングへの色気なども出しているようですが、まずは足元の問題を見直してもらえないものか、切に願う次第です。まあ実装マターで標準化のほうでではどうにもならないということなのかもしれませんが……

単体テスト

以前C++で開発をしていたころは単体テストが一般化する前でC++用テスティングフレームワークを自分で利用する機会はありませんでした。いま開発で単体テストを使わない選択肢は個人的にはないので、現状もっともメジャーなC++用テスティングフレームワークと思われるGoogleTestで初挑戦。まずはその圧倒的速さに感動しました。JavaScript用のJestとか無茶苦茶遅いですからね。もっともよく考えるとその速さは圧倒的に遅いコンパイルによって代替された見せかけという気もします。

Google Testは一体化したGoogle Mockをテストダブルに使えます。しかしテストダブルとするクラスは対象を抽象クラスから派生させなければならなりません(テストダブルは抽象クラスを継承して実現)。使えること自体がありがたいので言っても詮無いのですが、被テストコードの構造に影響を与えるという点でこれは本来なしでしかるべき制約です。そのように実装せざるを得ないことも理解できますが……。このあたり、やはり一昔前の言語という感は否めません。

また assertstd::bad_alloc といった単体テスト普及前から存在する一部言語機構は当然単体テストとの連携を考慮していないため、カバーできる範囲にはやむを得ない限界が残ります。C++の場合の正常系より異常系のほうを手厚くテストしたいというモチベーションがあると思うのですが、その点に制約ができてしまうのは残念というしかありません。まあこれも一昔前の言語故、このあたりを可能にするように手を加えていくとC++とは別物になっていくのでしょう。

おわりに

歴史が証明するとおり、いったん普及したプログラミング言語はこの世からはそう簡単には消え去りません。C++もそうした言語の一つとして、仕様は最新動向に追従し続け、利用シーン的にはそうした動きと関係したりしなかったりしながら息長く使われ続けていくのでしょう。つきあいはまだしばらく続きそうなので、私もスキルや知識の研鑽を重ねていきたいと思います。

(……でもやっぱりできればRustとか書きたいなあ……)

関連コンテンツ

Pick up work

最近のエントリ

アーカイブ

ブログ情報