2月5日、海外のテクノロジー/サイエンスメディア「Communications of the ACM」に「21世紀のC++(21st Century C++)」と題した記事が公開された。
この記事では、かつてのC++と比較して、現代のC++がどのように進化しているのかを、古いスタイルを知るエンジニアに向けて詳細に解説している。
以下に、その内容の一部を紹介する。内容に関心を持たれた方は、ぜひ原文を参照していただきたい。
20世紀のC++と何が違うのか?
C++は1980年代から存在する言語であり、後方互換性が強く保たれているため、当初のC++を知るエンジニアにとっては、以前のスタイルで使い続けることもできる。しかし現在では、型安全性やリソース管理を強化するために、言語仕様・標準ライブラリともに大きく変化している。
- **RAII(Resource Acquisition Is Initialization)の普及** コンストラクタとデストラクタを活用し、リソースやメモリを安全に管理する手法が推奨され、ライブラリもそれに合わせて提供されるようになった。
- **例外とエラー処理の明確化** 昔は`new`や`delete`、ポインタ操作であいまいなエラーを起こしやすかったが、いまは例外(`throw`)とエラーコードを使い分ける設計が一般的で、ライブラリ側でも例外安全を意識している。
- **ジェネリックプログラミングとコンセプト** テンプレートは昔からあったが、C++20で導入された「コンセプト」によって型要件をコンパイル時に検証しやすくなり、より安全でわかりやすいコードを書ける。
- **モジュールの導入** `#include`の代わりに`import`でコードを分割し、ビルド時間と依存関係の複雑さを大幅に低減できる仕組みがC++20で正式に入った。
- **コンパイル時評価(`constexpr` / `consteval`)の拡張** コンパイル時に関数を評価できる機能が大幅に強化され、パフォーマンス・安全性の両面で恩恵が大きい。
- **ガイドラインと静的解析ツールによる安全性向上** 型やポインタの使い方などについて、C++ Core Guidelinesなどが提示され、コンパイラや静的解析ツールがそれらをチェックする体制が整いつつある。
1. RAIIによるリソース管理
昔は手動でnew
/delete
を頻繁に使っていたかもしれないが、今のC++ではクラスのコンストラクタとデストラクタを活用し、リソースを所有するオブジェクト(ハンドル)を作るのが定石である。たとえば次のようなコードでは、unordered_map
を使って重複行を排除しているが、すべてのリソース管理はオブジェクトに隠蔽される。
import std; // 標準ライブラリをすべて利用
using namespace std;
int main()
{
unordered_map<string,int> m;
for (string line; getline(cin, line); )
if (m[line]++ == 0)
cout << line << '\n';
}
ここでは、明示的なdelete
操作は存在しない。スコープを抜ければ自動的にリソースが解放されるため、昔のようなメモリリークを心配する必要が大幅に減る。
2. Modulesでヘッダ地獄から解放
古いC++では大規模プロジェクトで#include
が大量に発生し、ビルド時間や依存関係の混乱がつきまとっていた。C++20のモジュール(modules)を使えば、ソース同士をimport
で明示的にやり取りできる(前節のサンプルコード先頭を参照のこと)。これにより、
- 読み込み順序によるバグが減る
- ビルド速度が大幅に改善される
- 不要なインクルードが減る
といった恩恵が得られる。適切にモジュールを切り分ければ、かつての「ヘッダファイルが巨大でインクルード先が無数にある」といった問題をかなり解消できる。
3. テンプレートとコンセプト
昔のテンプレートは、すべての型でコードがインスタンス化されるため、長大なエラーメッセージが出がちだった。C++20で導入されたコンセプトを使うと、型の要件を明示的に示せるため、コンパイル時のエラー報告がわかりやすくなる。たとえば、ソート対象がランダムアクセス可能で比較が定義されているかどうかをチェックし、そうでなければコンパイル時点で弾くことができる。
template<typename R, class Pred = ranges::less>
concept Sortable_range =
random_access_range<R> &&
sortable<iterator_t<R>, Pred>;
template<Sortable_range R>
void my_sort(R& r)
{
// 実装...
}
これにより、不適切な型でmy_sort
を呼び出すと、古いテンプレートほど混乱しないエラーが得られるようになった。
4. ガイドラインと安全性
昔のC++コードには、次のような問題がよくあった。
- 範囲外アクセス(`p[i]`が有効範囲を超えている)
- 解放後のポインタを使ってしまうダングリングポインタ
- 強引なキャストによる型破壊
現代C++では、C++ Core Guidelinesという一連の推奨事項と、それを自動チェックするツール群が提案・実装されている。たとえば範囲外アクセスを根絶するためにstd::span
を使い、配列とサイズを紐づけて安全なループを回す、といった手法が挙げられる。こうしたガイドラインがプロファイルという形で統合・強制されつつあり、必要に応じてコンパイラオプションやアノテーションで制約をかける運用が進んでいる。
まとめ
古いC++を知るエンジニアが現代C++に触れる際、以下のポイントが大きな変化である。
- **RAII**でリソース管理を自動化し、メモリリークリスクを減らす
- **モジュール**でビルドを高速化し、複雑な依存関係を整理する
- **コンセプト**と拡張されたテンプレート機能でジェネリックプログラミングを安全かつ明確に行う
- **ガイドライン**と解析ツールによって、昔からの危険な書き方を防ぎ、型安全・リソース安全を高める
これらの機能はすべて後方互換性を重視して設計されているため、既存のコードベースを段階的に移行することが可能である。昔ながらのC++の知識に加え、現代的な設計やライブラリの活用法を学ぶことで、より安全で効率的な開発が期待できる。
詳細は21st Century C++ – Communications of the ACMを参照していただきたい。