はじめに
「Modern C++チャレンジ ―C++17プログラミング力を鍛える100問」を通読しました。
この本は文法書ではなく、実世界寄りの課題を100問並べ、それぞれに解答コードと解説を付けた問題集です。
C++の言語機能・標準ライブラリに加えて、Asio、Crypto++、libcurl、nlohmann/json、pugixml、SQLiteといったサードパーティライブラリまで「C++でどう実務を書くか」の範囲に含めているのが特徴です。
本記事では、設計・アーキテクチャに関心のあるエンジニア向けに、章ごとの内容と「設計の学びどころ」を整理します。
本記事のコードは、本書の考え方を説明するために私が書き直した短い例です。本書のコードそのものではありません。
本書の概要
| 項目 | 内容 |
|---|---|
| 構成 | 12章・100問(難易度は易・中・難がほぼ同数) |
| 形式 | 各章が「問題」から始まり、続けて「解答」が並ぶ |
| 対象 | C++の文法をひととおり学んだ人 |
| 前提 | C++17対応コンパイラ、CMake |
| 特徴 | 解答中のコードからあえてインクルードを省き、読者に考えさせる |
本書は言語仕様を教えるものではありません。
まえがきでも、まず自分で解いてから解答を読むことが推奨されています。
どんな人に向いているか
- 文法は一通り分かるが、実際に手を動かす題材が足りない人
- 枯れたC++(C++03/11)の知識をC++17にアップデートしたい人
- 標準ライブラリで足りない部分に、どのライブラリを当てるか知りたい人
- 設計パターンをC++でどう書くか見たい人
逆に、C++を初めて学ぶ段階の人には厳しいと思います。
テンプレートや例外安全をある程度知っている前提で解説が進みます。
章ごとの要点
1章 数学の問題
最大公約数、素数、友愛数、グレイコード、ローマ数字、コラッツ数列、ISBN検証など、ウォーミングアップの14問です。
設計面で面白いのは、**アルゴリズム自体より「計算量とオーバーフローへの目配り」**が随所に出てくる点です。
- 総和は
intではなくunsigned long longを使う - 約数の列挙は平方根までで打ち切る
- コラッツ数列は途中結果をキャッシュして再計算を避ける(ただしメモリとのトレードオフがある)
2章 言語機能
IPv4クラス、2次元配列、可変引数関数、システムハンドルのラッパー、温度リテラルなど、C++らしい型設計の章です。
特に学びが大きいのは次の3点です。
-
RAIIによるハンドル管理
Windowsのハンドルを例に、閉じ忘れや例外で漏れる問題を、ラッパークラスで構造的に潰します。
ハンドルごとの差(無効値は何か、どう閉じるか)は traitsクラス に追い出す設計です。 -
畳み込み式(C++17)による可変引数の簡略化
再帰テンプレートで書いていた処理が、1行で書けます。 -
型で単位を区別する
温度をquantity<scale>のようなテンプレートで表し、単位の取り違えをコンパイル時に弾きます。
template <typename C, typename... Args>
void push_all(C& c, Args&&... args)
{
(c.push_back(std::forward<Args>(args)), ...);
}
また、contains_any / contains_all のように、畳み込み式の短絡評価を活かす書き方は、実務でもそのまま使えます。
3章 文字列と正規表現
16進文字列変換、キャピタライズ、分割・連結、最長回文部分文字列、URLパース、日付フォーマット変換などです。
- 最長回文は、総当たり(O(N³))から、部分問題の表を使ったDP(O(N²))への改善が示されます
- URLの分解では、必須項目と任意項目を
std::optionalで表現します - 日付変換は
regex_replaceとキャプチャグループで済ませます
正規表現は、「完璧な正規表現を作る」ことより「regexライブラリを使いこなす」ことが目的と明言されており、その割り切りに好感が持てました。
4章 ストリームとファイルシステム
パスカルの三角形、プロセス一覧の表形式出力、空行除去、ディレクトリサイズ計算、古いファイルの削除などです。
押さえておきたい点です。
- ディレクトリサイズの合計には
std::uintmax_tを使う(2GB超を想定) - 古いファイルの削除で、
recursive_directory_iteratorの走査中に削除すると未定義動作になり得るため、再帰関数+directory_iteratorで書く - 一時ログファイルは、デストラクタで確実に消すRAIIで実装する
「走査しながら変更しない」という注意は、ファイル操作に限らずコンテナ全般で効く原則です。
5章 日付と時間
実行時間計測、日数差、曜日、週番号、タイムゾーン変換、月間カレンダーです。
出版当時、C++20の chrono カレンダー/タイムゾーン拡張は策定中だったため、その元になった date ライブラリで解く構成です。
名前空間を置き換えれば移行できる、というスタンスで書かれています。
C++20はすでに標準化されています。
現在の環境では、date ライブラリではなく標準の std::chrono で置き換えて試すと、より実践的な学びになります。
計測関数のテンプレート perf_timer は、std::invoke で任意の関数と引数を受け、tick数ではなく duration を返す設計です。
単位が型に残るため、秒とミリ秒を混ぜて足し合わせても壊れません。
6章 アルゴリズムとデータ構造
本書で最も長い章です。優先度付きキュー、リングバッファ、ダブルバッファ、最頻出要素、順列生成、ペア作成、zip、クイックソート、ダイクストラ法、イタチプログラム、ライフゲームなどを扱います。
設計観点では次が印象に残りました。
-
優先度付きキュー:
std::vector+ヒープ操作(push_heap/pop_heap)で自作する - リングバッファ: 論理的な並びと物理的な配置がずれるため、イテレータを自前で実装する必要がある
-
ダブルバッファ: 書き込み用と読み取り用を
swapで入れ替え、読み取り側はミューテックスで守る -
ダイクストラ法: 優先度付きキューではなく
std::setを使い、キー更新(削除→再挿入)を対数時間で行う - クイックソート: 再帰版に加え、スタックを使った反復版も示される
select(射影)や zip のような、他言語で馴染みのある高階関数をC++で書く問題もあります。
std::transform との関係を理解するのにちょうどよい題材です。
7章 並行処理
並列変換、並列min/max、並列クイックソート、スレッドセーフなロガー、カスタマーサービスのシミュレーションです。
ここでの設計の肝は次のとおりです。
-
要素数がしきい値以下なら逐次処理
並列化にはコストがあるため、小さいデータでは逆に遅くなります -
std::threadとstd::asyncの書き分け
同じ「分割して集約する」処理を、スレッド版と非同期関数版で比較できます -
ロガーはシングルトン+ミューテックス
C++11以降、関数内staticの初期化はスレッドセーフであることを利用します -
condition_variableによる待ち合わせ
窓口スレッドと顧客スレッドの協調動作で学べます
C++17では、標準アルゴリズムに実行ポリシー付きの並列版オーバーロードが入っています。
自作する意味は、「内部で何が起きているか」を理解することにあります。
8章 デザインパターン
アーキテクチャに関心のある方には、この章が一番おすすめです。
問題の性質から、どのパターンが適切かを導く構成になっています。
| 問題 | パターン | ポイント |
|---|---|---|
| パスワード検証 | Decorator | 検証ルールを積み重ねて組み合わせる |
| ランダムパスワード生成 | Composite | 部品と合成物を同じインターフェースで扱う |
| 社会保障番号の生成 | Template Method | 手順は共通、国ごとに差分を派生クラスで定義 |
| 経費の承認 | Chain of Responsibility | 権限を超えたら上司に回す |
| 観察可能なvector | Observer | 状態変化を登録者に通知 |
| 値引き計算 | Strategy | 値引きや計算方法を差し替え可能にする |
どれもクラス図つきで解説され、「なぜこのパターンか」が説明されます。
GoFの本で学んだパターンを、現代的なC++(unique_ptr での所有権表現など)で書き直す練習にもなります。
9章 データシリアライゼーション
XML(pugixml)、JSON(nlohmann/json)、PDF生成(PDF-Writer)です。
- XMLは、DOM的な走査とXPath式の両方を体験できる
- JSONは、
to_json/from_jsonを用意すると、自作型と標準コンテナの変換が暗黙に行われる - JSONの読み取りでは、キーがないと例外になる
at()を使い、operator[]は避ける
ライブラリ選定の考え方(軽量さ、依存の少なさ、ドキュメント、実績)が丁寧に書かれており、「どれを選ぶか」の判断材料としても読めます。
10章 アーカイブ、画像、データベース
Zip圧縮・解凍(ZipLib)、PNG生成(PNGWriter)、バーコード、SQLiteが題材です。
- 国旗や、Captcha風画像、EAN-13バーコードなど、出力が目で見えるので楽しい
- SQLiteはトランザクション(begin / commit / rollback)で複数テーブルへの挿入を原子的に行う
- 画像などは
BLOBとして格納するか、ファイルに分けるか、というトレードオフが解説される
データベース設計の観点では、メディアファイルの扱い(サイズによる格納先の判断)が参考になります。
11章 暗号
シーザー暗号、ヴィジュネル暗号、Base64の自前実装、ハッシュ、AES、RSA署名です。
- 古典暗号は「実用ではなく練習」と明確に断っている
- Base64は、符号化と復号の表を分けて実装する
- パスワードは平文保存せず、ハッシュで照合する
- ファイル署名は、秘密鍵で署名し、公開鍵で検証する
MD5は脆弱であり、下位互換のために提供されているだけです。
本書でも注意が書かれていますので、実務では使わないようにしましょう。
12章 ネットワークとサービス
ホストのIP取得、TCPのクライアント・サーバ(Fizz-Buzz)、REST API呼び出し、IMAP、翻訳API、顔検出APIです。
-
Asio による非同期I/O(
sessionとserverの分離) - libcurl(curlcpp) によるHTTPリクエスト
- 外部APIの応答は、成功と失敗で形が違うため、エラー応答もモデル化してデシリアライズする
外部サービスを呼ぶコードの構造(設定を持つクラス、リクエスト組み立て、応答パース、エラー処理)が整理されて見えます。
良かった点
-
「モダン」の範囲が実践的
言語機能だけでなく、周辺ライブラリやAPIまで含めて「アプリを作るために必要なもの」が揃っています。 -
問題が多様
数学、文字列、I/O、並行処理、パターン、暗号、ネットワークと、一冊で実務の幅が見えます。 -
解答に設計の理由が書かれている
コードだけでなく、なぜその選択をしたか(vectorを選ぶ理由、operator()を使う理由など)が書かれています。 -
練習問題・発展問題が随所にある
解答を読んで終わりにならないよう、自分で拡張する課題が挟まれています。
気をつけたい点
-
文法の説明はない
テンプレートやムーブセマンティクスの基礎は、別途学んでおく必要があります。 -
外部ライブラリは、時間とともに状況が変わる
クラウドAPIはその典型です。解答どおりに動かない場合は、最新のドキュメントを参照してください。 -
コードは、そのまま写経せず検証しながら読む
読んでいて、細かい不整合に気づく箇所がいくつかありました。たとえば、データベースから映画を読む関数で、脚本家の取得に監督用の関数を呼んでいるように見える箇所があります。
「疑いながら読む」こと自体が、よい訓練になります。 -
C++20以降の機能は含まれない
std::format、ranges、conceptsなどは出てきません。現代の書き方に置き換える練習をするとよいです。
設計の視点で得られたこと
最後に、設計・アーキテクチャの観点で持ち帰れる学びをまとめます。
- リソースの所有はRAIIで型に閉じ込める(ハンドル、ログファイル、一時ファイル)
- 差分はtraitsやテンプレートで外に出す(ハンドルの閉じ方、単位変換)
- パターンは「問題の形」から選ぶ(検証はDecorator、承認はChain of Responsibility)
- 並列化はしきい値で切り替える(小さいデータでは逐次が有利)
- 外部サービスは、設定・通信・パース・エラー処理を分けて持つ
- 標準ライブラリで足りないときは、選定基準を言語化してから採用する
おわりに
「Modern C++チャレンジ」は、C++の文法を覚えたあとの次の一歩にちょうどよい一冊です。
全問を解く必要はなく、気になる章から手を動かすだけでも十分に得るものがあります。
私のおすすめの読み順です。
- まず2章(型設計とRAII)
- 次に8章(デザインパターン)
- その後、興味のある分野の章(並行処理、ネットワークなど)
設計やアーキテクチャに関心のある方は、ぜひ8章から手に取ってみてください。