第1章:C++の歴史と設計思想
はじめに
プログラミング言語を深く理解するには、その言語が「なぜ」生まれたのか、「どのような問題」を解決しようとしたのかを知ることが不可欠です。C++は1979年に誕生しましたが、その設計思想を理解するには、さらに遡ってオブジェクト指向プログラミングの起源を知る必要があります。
本章では、Simula(1967年)からC++(1983年)への歴史的な流れを追い、C++の設計思想である「ゼロオーバーヘッド原則」と「Cとの互換性」について学びます。
---
1. オブジェクト指向の起源:Simula(1962-1967)
1.1 ノルウェー計算センターでの誕生
世界初のオブジェクト指向言語Simulaは、ノルウェーのオスロにあるNorwegian Computing Center(ノルウェー計算センター)で誕生しました。開発者はOle-Johan DahlとKristen Nygaardの二人です。
彼らは当初、離散イベントシミュレーション(船の入出港、工場の生産ライン、交通システムなど)を効率的に記述するための言語を設計していました。
1.2 シミュレーションからの発想
船の入出港をシミュレートする問題を考えてみましょう。従来の手続き型プログラミングでは、以下のようなデータと手続きを別々に定義する必要がありました:
/* C言語的な手続き型アプローチ */
struct Ship {
int id;
int cargo;
int position;
};
struct Port {
int capacity;
int current_ships;
};
void dock_ship(struct Ship *ship, struct Port *port) {
/* 入港処理 */
}
void load_cargo(struct Ship *ship, int amount) {
/* 積荷処理 */
}
しかし、DahlとNygaardは気づきました。現実世界では「船」は自分自身の状態(積荷量、位置)と振る舞い(入港する、積荷する)を持っている。なぜプログラムでもそのように表現できないのか?
この発想から、クラスとオブジェクトの概念が生まれました。
1.3 Simula 67の革命
1967年にリリースされたSimula 67は、以下の革命的な概念を導入しました:
| 概念 | 説明 | |------|------| | クラス | データと手続きをカプセル化する設計図 | | オブジェクト | クラスから生成される実体(インスタンス) | | 継承 | 既存クラスを拡張して新しいクラスを定義 | | 仮想関数 | 派生クラスでの振る舞いの変更 |
DahlとNygaardは1966年の論文で次のように述べています:
> "The class concept in SIMULA 67 provides a mechanism for defining new data types, together with operations on objects of those types." > > (Simula 67のクラス概念は、新しいデータ型とその型のオブジェクトに対する操作を定義するメカニズムを提供する)
この論文は2001年のチューリング賞受賞につながりました。
1.4 Simulaの限界
Simulaは概念的には画期的でしたが、実用面では問題がありました:
- 実行速度の遅さ: ガベージコレクション、動的型チェックによるオーバーヘッド
- システムプログラミングに不向き: 低レベルのメモリ操作が困難
- 普及の限界: ALGOLベースのため、Cが主流になった時代に取り残された
これらの問題が、後のC++誕生の動機となります。
---
2. C with Classesの誕生(1979年)
2.1 Bjarne Stroustrupの背景
Bjarne Stroustrupは1979年、AT&Tベル研究所で働き始めました。彼は博士研究でSimulaを使用しており、その抽象化能力に深く感銘を受けていました。
しかし同時に、彼はSimulaの実行速度の遅さに不満を持っていました。彼の博士研究では、分散システムのシミュレーションを行っていましたが、Simulaの遅さが研究の妨げになっていました。
2.2 設計目標:SimulaのパワーとCの効率
ベル研究所でStroustrupが直面したのは、UNIXカーネルの解析という実践的な問題でした。この問題を解決するため、彼は以下の設計目標を持った言語を構想しました:
| 目標 | 由来 | |------|------| | クラスと継承 | Simulaから | | 高い実行効率 | C言語から | | システムプログラミング | UNIX環境の要求 | | 既存コードとの互換性 | 実用性の要求 |
Stroustrupは後に次のように述べています:
> "C++ was designed to provide Simula's facilities for program organization together with C's efficiency and flexibility for systems programming." > > (C++は、Simulaのプログラム組織化機能と、Cのシステムプログラミングにおける効率性と柔軟性を兼ね備えるよう設計された)
2.3 Cfront:C++からCへのトランスレータ
1979年、Stroustrupは「C with Classes」と呼ばれる言語を開発しました。これはC++の前身です。
興味深いことに、最初のC++コンパイラは実際にはコンパイラではありませんでした。Cfrontと呼ばれるこのツールは、C++コードをC言語のコードに変換するトランスレータでした。
C++ソース → Cfront → Cソース → Cコンパイラ → 実行ファイル
この設計には重要な意味がありました:
- 移植性: 既存のCコンパイラがある環境ならどこでも使える
- 効率性: 生成されたCコードは通常のCプログラムと同等の効率
- 互換性: CのコードをそのままC++で使える
2.4 主要な追加機能
C with Classesで追加された主な機能:
// クラスの定義
class Ship {
private:
int cargo;
int position;
public:
Ship(int initial_cargo); // コンストラクタ
~Ship(); // デストラクタ
void dock(Port& port);
void load(int amount);
};
C言語との比較:
| 機能 | C | C with Classes | |------|---|----------------| | データと関数のグループ化 | 構造体 + 関数ポインタ | クラス | | 初期化 | 手動 | コンストラクタ | | 解放 | 手動 | デストラクタ | | アクセス制御 | なし | private/public |
---
3. C++への進化(1983年〜現在)
3.1 名前の由来
1983年、Rick Mascittiが「C++」という名前を提案しました。これはC言語のインクリメント演算子(++)に由来しています。
int c = 0;
c++; // cを1増やす
// つまり、C++ = Cを一段階進化させたもの
ちなみに、「++C」ではなく「C++」が選ばれた理由について、Stroustrupはジョークで次のように述べています:
> "C++'s name is a pun on the C increment operator ++, and a joke on the language's tendency to be 'one step behind' what's truly needed."
3.2 標準化の歴史
C++は30年以上にわたって進化を続けてきました:
| 年 | 標準 | 主要な追加機能 | |----|------|--------------| | 1979 | C with Classes | クラス、継承 | | 1983 | C++ | 仮想関数、演算子オーバーロード | | 1985 | C++ 1.0 | 最初の商用リリース、The C++ Programming Language出版 | | 1990 | C++ 2.0 | 多重継承、抽象クラス | | 1998 | C++98 | 最初のISO標準、STL、例外、テンプレート | | 2003 | C++03 | バグ修正版 | | 2011 | C++11 | モダンC++の始まり、auto、ラムダ、ムーブセマンティクス | | 2014 | C++14 | ジェネリックラムダ、変数テンプレート | | 2017 | C++17 | 構造化束縛、std::optional | | 2020 | C++20 | concepts、ranges、coroutines | | 2023 | C++23 | std::expected、改善されたranges |
3.3 42で使用するC++バージョン
42のCPP ModulesではC++98が基本となります。これには教育的な理由があります:
- 基礎の理解: モダンC++の便利機能に頼らず、基本を理解する
- 手動管理の経験: スマートポインタなしでメモリ管理を学ぶ
- 歴史的理解: 言語の進化を追体験する
ただし、C++11以降の機能も理解しておくことで、将来的なコードの可読性と保守性が向上します。
---
4. C++の設計思想
4.1 ゼロオーバーヘッド原則
C++の最も重要な設計思想はゼロオーバーヘッド原則(Zero-Overhead Principle)です:
> "What you don't use, you don't pay for. And further: What you do use, you couldn't hand code any better." > > (使わないものにはコストを払わない。さらに、使うものについては、手書きでこれ以上効率的には書けない) > > — Bjarne Stroustrup
これは具体的に何を意味するのでしょうか?
例1:仮想関数を使わない場合
class Point {
int x, y;
public:
int getX() { return x; } // 仮想関数ではない
};
このクラスのインスタンスは、C言語の構造体と全く同じメモリレイアウトを持ちます。仮想関数を使わないなら、仮想関数テーブル(vtable)のオーバーヘッドは発生しません。
例2:仮想関数を使う場合
class Shape {
public:
virtual double area() = 0; // 仮想関数
};
仮想関数を使う場合のみ、vtableへのポインタ(通常8バイト)がオブジェクトに追加されます。しかし、これは動的ディスパッチを実現するための最小限のコストです。
4.2 Cとの互換性
C++はCのスーパーセットとして設計されました(完全ではありませんが、ほぼ互換):
/* このCコードは... */
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
// そのままC++としてコンパイル可能
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
extern "C"
CとC++を混合する場合、名前マングリングの違いに注意が必要です:
// C++では関数名がマングリングされる
int add(int a, int b); // 実際は _Z3addii のような名前になる
// Cのライブラリを使う場合
extern "C" {
int c_function(int x); // Cの命名規則を使用
}
4.3 抽象化 vs 効率性のバランス
C++は「高レベルの抽象化」と「低レベルの効率性」の両立を目指しています:
| 抽象化レベル | C++ の機能 | |------------|-----------| | 高レベル | クラス、テンプレート、STL | | 中レベル | 参照、演算子オーバーロード | | 低レベル | ポインタ、ビット操作、インラインアセンブラ |
Stroustrupの言葉:
> "C++ is designed to allow you to express ideas, but it forces you to decide what you want." > > (C++はアイデアを表現できるように設計されているが、何が欲しいかを決めることを強制する)
---
5. CとC++の関係
5.1 互換性と相違点
C++はCからの移行を容易にするため、高い互換性を持っていますが、いくつかの重要な違いがあります:
型安全性の強化
/* C言語:暗黙的な型変換が許される */
void* ptr = malloc(sizeof(int));
int* iptr = ptr; // OK in C
// C++:明示的なキャストが必要
void* ptr = malloc(sizeof(int));
int* iptr = static_cast<int*>(ptr); // 明示的キャストが必要
宣言と定義
/* C言語:関数プロトタイプなしでも呼び出し可能(暗黙の宣言) */
int main() {
foo(42); // 警告は出るが動く場合がある
return 0;
}
// C++:関数は使用前に宣言が必要
void foo(int x); // 宣言が必要
int main() {
foo(42);
return 0;
}
5.2 CからC++への移行
42でCを学んだ後、C++に移行する際の主な変更点:
| C言語 | C++ |
|-------|-----|
| malloc/free | new/delete |
| 構造体 | クラス |
| 関数ポインタ | 仮想関数、ファンクタ |
| printf/scanf | cout/cin |
| マクロ関数 | インライン関数、テンプレート |
| void* | テンプレート、ジェネリック |
---
6. なぜ歴史を学ぶのか?
6.1 「なぜ」の理解
言語の歴史を知ることで、仕様書の「なぜ」が理解できます:
- なぜC++にはガベージコレクションがないのか? → ゼロオーバーヘッド原則
- なぜ仮想デストラクタが必要なのか? → Simulaの継承モデルとC++のメモリモデルの融合
- なぜテンプレートは複雑なのか? → 実行時オーバーヘッドなしのジェネリクスを実現するため
- パフォーマンスが重要な場合: 仮想関数の使用を避け、テンプレートを使う
- 拡張性が重要な場合: 仮想関数と継承を活用
- Cとの互換性が必要な場合: extern "C"を適切に使用
- Simula(1967): オブジェクト指向の起源、クラス・継承・仮想関数の発明
- C with Classes(1979): StroustrupによるSimulaとCの融合
- C++の進化: C++98からC++23までの標準化の歴史
- 設計思想: ゼロオーバーヘッド原則、Cとの互換性
- CとC++の関係: 互換性と相違点、移行のポイント
- Dahl, O-J. & Nygaard, K. (1966). "SIMULA - An ALGOL-Based Simulation Language", Communications of the ACM
- Stroustrup, B. (1994). "The Design and Evolution of C++", Addison-Wesley
- Stroustrup, B. (2013). "The C++ Programming Language" (4th Edition), Addison-Wesley
- ISO/IEC 14882:2020 - Programming Language C++
- Simulaが「シミュレーション」という問題領域から生まれた理由を説明してください。
- C++の「ゼロオーバーヘッド原則」を自分の言葉で説明してください。
- extern "C"が必要な場面を一つ挙げ、その理由を説明してください。
- C++98とC++11の主な違いを3つ挙げてください。
6.2 設計判断への応用
歴史的背景を知ることで、自分のコードでも適切な設計判断ができます:
---
まとめ
本章で学んだこと:
次章では、C++の基本構文(iostream、名前空間、参照など)について、Cとの比較を交えながら学んでいきます。
---