この章では、プログラミング言語C++の歴史をたどり、ISO規格の進化や『プログラミング言語C++』の各版の寄与を明らかにします。初期のC++から現代の標準まで、言語機能や設計思想の変遷を探り、読者に深い理解を提供します。
C++の発祥は、1979年から1983年にかけて行われたBjarne Stroustrupによる研究として始まりました。Stroustrupは、C言語の拡張として新しい言語を開発し、オブジェクト指向プログラミングを統合することを目指しました。
C++言語の歴史において、「C with Classes（クラスを備えたC）」や「cfront」は重要な要素となります。これらは、C++がどのようにして生まれ、進化していったかを理解する上で鍵となる要素です。
C with Classes:
定義: C with Classesは、Bjarne Stroustrupによって初めて開発されたC++の前身となる言語です。
特徴: 1979年から1983年にかけて、StroustrupはC言語にクラス機能を追加したものを開発しました。これにより、オブジェクト指向プログラミングの基本的な概念が初めて導入されました。
動機: C with Classesの目的は、クラスとオブジェクト指向の概念を導入することで、ソフトウェアの設計や保守性を向上させることでした。
cfront:
定義: cfrontは、C++コンパイラの初期の実装で、C++のソースコードをC言語に変換してからコンパイルする方式を採用していました。
特徴: cfrontは、1983年にリリースされ、C++言語の普及に大きく寄与しました。このコンパイラを通じて、C++はC言語の拡張として広まっていきました。
動機: 当初、C++の構文や概念は、それに対応するC言語の構文に変換され、既存のC言語のコンパイラで動作するように設計されました。これにより、既存のCプログラマがC++に移行しやすくなりました。
これらの要素は、C++が生まれ、成長していく過程での基盤となりました。C++言語が発展していく中で、これらの初期のアプローチが現代のC++言語仕様へと続く重要なステップとなっています。
初期のC++言語は、クラス、メンバ関数、派生、仮想関数、コンストラクタ・デストラクタ、アクセス修飾子、演算子オーバーロードなど、オブジェクト指向プログラミングの基本概念を導入していました。これにより、プログラマはより柔軟で保守しやすいコードを記述できるようになりました。
初期のC++での基本的な機能であるクラス、メンバ関数、派生、仮想関数、コンストラクタ・デストラクタ、アクセス修飾子、演算子オーバーロードなどを含む簡単なコードを以下に示します。このコードはC++の初期のバージョンでの機能を示しており、現代のC++とは異なる点がいくつかあります。
// クラスの定義
class Animal {
private:
    char* name;
public:
    // コンストラクタ
    Animal(const char* n) {
        // メモリを確保して名前をセット
        name = new char[strlen(n) + 1];
        strcpy(name, n);
        cout << "Animal constructor: " << name << endl;
    }
    // デストラクタ
    ~Animal() {
        cout << "Animal destructor: " << name << endl;
        // メモリを解放
        delete[] name;
    }
    // メンバ関数
    void makeSound() const {
        cout << "Animal makes a sound" << endl;
    }
    // << 演算子のオーバーロード
    friend ostream& operator<<(ostream& os, const Animal& animal) {
        os << "Animal: " << animal.name;
        return os;
    }
};
// 派生クラス
class Dog : public Animal {
public:
    // 派生クラスのコンストラクタ
    Dog(const char* n) : Animal(n) {
        cout << "Dog constructor: " << n << endl;
    }
    // 派生クラスのデストラクタ
    ~Dog() {
        cout << "Dog destructor" << endl;
    }
    // 仮想関数をオーバーライド
    void makeSound() const {
        cout << "Dog barks" << endl;
    }
    // << 演算子のオーバーロード
    friend ostream& operator<<(ostream& os, const Dog& dog) {
        os << "Dog: " << dog.name;
        return os;
    }
};
int main() {
    // 基底クラスのオブジェクト
    Animal genericAnimal("Generic Animal");
    cout << genericAnimal << endl;
    cout << endl;
    // 派生クラスのオブジェクト
    Dog buddy("Buddy");
    cout << buddy << endl;
    return 0;
}
このコードでは、Animalクラスが基底クラスとして、Dogクラスがそれを派生しています。クラス内にはコンストラクタ、仮想関数、デストラクタが含まれており、それぞれの動作が実装されています。main関数では、AnimalクラスとDogクラスのオブジェクトを生成して、それぞれのメソッドを呼び出しています。
参照（Reference）:
導入タイミング: 参照はC++の初期の段階から存在しています。C++言語は、Bjarne Stroustrupによって1979年から1983年にかけて開発され、その初期の段階から参照が導入されました。参照は、ポインタの代替手段として導入され、特に関数の引数や返り値、オブジェクトへのエイリアスとして利用されています。
void modifyValue(int& ref) {
    ref = 42;
}
int main() {
    int value = 10;
    modifyValue(value);
    
    cout << "Modified value: " << value << endl;
    return 0;
}
プログラミング言語C++の最初のバージョンは、Bjarne Stroustrupによって1985年に発表されました。この初版では、オブジェクト指向プログラミングの概念が初めて公式に導入され、C++の基本的な特徴が確立されました。この時点でのC++は、現代の標準とは異なる特徴を持っていましたが、その基盤が築かれていくこととなりました。
初期のC++およびC言語のスコープに関するルールは、現代の標準とは異なっていました。特に、プログラミング言語C++第1版では、forループ内で宣言された変数は、ループブロックの外部でも有効でした。これは、その後の規格で変更され、より厳密なスコープ規則が導入されました。
以下に、初期のC++および現代のC++の例を示します：
プログラミング言語C++第1版 (1985年)
int main() {
    for (int i = 0; i < 5; ++i) {
        // ループ内で宣言された変数iは、ループの外でも有効
        cout << "Inside loop: " << i << endl;
    }
    // ループ外でも変数iは有効
    cout << "Outside loop: " << i << endl;
    return 0;
}
現代のC++
int main() {
    for (int i = 0; i < 5; ++i) {
        // ループ内で宣言された変数iは、ループの外では無効
        std::cout << "Inside loop: " << i << std::endl;
    }
    // ループ外では変数iは無効（エラー）
    // std::cout << "Outside loop: " << i << std::endl;
    return 0;
}
この変更は、変数がその宣言されたブロックまたはスコープ内でのみ有効であるというより厳格なスコープ規則に準拠しています。プログラミング言語の進化に伴い、より安全で予測可能なコードの記述が求められるようになりました。
当初のC++がC言語から進化する中で導入されたキーワードには、以下のようなものがあります。
C++では、class キーワードが導入され、これはオブジェクト指向プログラミングの基本的な要素であるクラスを定義するために使用されます。クラスはデータメンバとメンバ関数を含み、オブジェクトの設計図となります。
new 演算子は動的メモリの割り当てを行います。これにより、ヒープ領域にオブジェクトが作成されます。対応する delete 演算子は動的に割り当てられたメモリを解放します。
const キーワードは変数や関数の定義時に使用され、その宣言を定数として扱います。これにより、変更できない定数が定義されます。
inline キーワードは関数定義前に付けられ、関数呼び出しをインライン展開し、コンパイラによる最適化を促進します。
virtual キーワードは仮想関数を宣言します。これにより、派生クラスが仮想関数をオーバーライドでき、実行時に動的なポリモーフィズムが実現されます。
friend キーワードはフレンド関数を宣言し、その関数がクラスの非公開メンバにアクセスできるようにします。
アクセス修飾子として、private、protected、public キーワードが導入されました。これらはクラスのメンバへのアクセス権を制御し、カプセル化を実現します。private はクラス内からのみアクセス可能、protected はクラスおよび派生クラスからアクセス可能、public はどこからでもアクセス可能です。
最初の標準化されたC++規格。このバージョンでは、多くの基本的なC++の特徴や言語機能が導入されました。STL（Standard Template Library）もこのバージョンで初めて規格化されました。
C++98より前:
C++98以降:
   
C++98以降:
int main() {
    bool flag = true;
}
   
C++98より前:
// C++98
int main() {
    cout << "Hello, World!" << endl;
}
C++98以降:
using namespace std;
int main() {
    cout << "Hello, World!" << endl;
}
   
これらはC++98への移行の一般的な変更点です。
C++98の修正版で、主にいくつかの誤りや曖昧な部分を修正し、標準ライブラリに一部の拡張を加えたもの。言語仕様には大きな変更はありませんでした。
新機能 std::auto_ptr（ただし非推奨）
C++03では、動的メモリ管理のためのstd::auto_ptrが導入されました。ただし、これはC++11以降でより安全な代替手段が導入されるまで使用されましたが、C++11以降では非推奨となりました。
int main() {
    std::auto_ptr myPtr(new int(42));
    std::cout << "Value: " << *myPtr << std::endl;
    return 0;
}
   
2011年に導入された最初の大規模なアップデート。このバージョンでは、自動型推論（autoキーワード）、スマートポインタ、範囲ベースのforループ、ラムダ式など、多くの新しい言語機能が追加されました。
// C++11以降
auto myNumber = 42; // int型として推論される
// C++11以降
int main() {
    std::unique_ptr myUniquePtr(new int(42));
    return 0;
}
// C++11以降
int main() {
    std::vector numbers = {1, 2, 3, 4, 5};
    for (const auto& num : numbers) {
        // numはベクターの各要素に対する参照として自動的に推論される
    }
    return 0;
}
// C++11以降
auto add = [](int a, int b) { return a + b; };
int result = add(3, 4);
// C++11以降
std::string oldString = "Hello, C++11!";
std::string&& movedString = std::move(oldString);  // ムーブ演算子を使用して移動
nullptrが導入され、ポインタのnull表現として推奨されるようになりました。
// C++11以降
int* myPtr = nullptr;
C++11の修正版で、新しい言語機能は追加されず、いくつかのバグが修正されました。このバージョンは、標準ライブラリの改善や拡張が主でした。
C++11からC++14への変更は、主に言語機能の拡張やライブラリの改善が行われたものです。以下は、C++11からC++14への変更の主な注意点とそれに関連する例です。
// C++14
int binaryLiteral = 0b1101'1010'0111;
// C++14
struct Point {
    int x;
    int y;
};
Point p{1, 2};
// C++14
int x = 42;
decltype(auto) y = x;  // yの型はint
// C++14
auto add = [](auto a, auto b) { return a + b; };
// C++14
auto myUniquePtr = std::make_unique(42);
// C++14
int x = 42;
decltype(x) y = x;  // yの型はint
C++14からC++17への変更は、主に言語仕様の追加やライブラリの拡張が行われたものです。以下は、C++14からC++17への変更の主な注意点とそれに関連する例です。
// C++17
std::pair myPair = {42, 3.14};
auto [x, y] = myPair; // 構造化バインディング
// C++17
if (int value = getValue(); value > 0) {
    // valueを使用した処理
}
// C++17
template 
void process(T value) {
    if constexpr(std::is_integral::value) {
        // 整数型の処理
    } else {
        // それ以外の型の処理
    }
}
// C++17
std::optional getOptionalValue() {
    return std::optional(42);
}
// C++17
int main() {
    std::string_view myStringView("Hello, World");
    std::cout << myStringView.substr(0, 5) << std::endl; // Hello
    return 0;
}
範囲ベースforループで初期化式が外部スコープに制限されていなくなりました。
int main() {
    std::vector numbers = {1, 2, 3, 4, 5};
    for (auto& num : numbers) {
        num *= 2;
    }
    for (const auto& num : numbers) {
        std::cout << num << " ";
    }
    return 0;
}
基底クラスのコンストラクタを派生クラスで呼び出すための新しい構文が追加されました。
class Base {
public:
    Base(int x) { /* ... */ }
};
class Derived : public Base {
public:
    using Base::Base; // 基底クラスのコンストラクタを利用
    // 派生クラスの特有の初期化処理
};
C++17では、ファイルシステム操作を行うための新しいライブラリが導入されました。
int main() {
    std::filesystem::path path = "/path/to/directory";
    if (std::filesystem::exists(path)) {
        std::cout << "Directory exists." << std::endl;
    } else {
        std::cout << "Directory does not exist." << std::endl;
    }
    return 0;
}
C++17のヘッダーには、Parallel STL (Parallel Standard Template Library) が含まれています。これにより、標準アルゴリズムを並列処理するための関数が提供されています。しかし、注意が必要で、コンパイラが並列処理をサポートしている必要があります。
以下は、ヘッダーから導入された一部のParallel STL関数の例です。
int main() {
    std::vector numbers = {5, 2, 8, 3, 1, 7, 4, 6};
    // Parallel STLの例: 並列ソート
    std::sort(std::execution::par, numbers.begin(), numbers.end());
    // 並列でソートされた結果を表示
    std::cout << "Parallel Sorted Numbers: ";
    for (const auto& num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
    return 0;
}
 
この例では、std::execution::parを使用して、std::sort関数を並列に実行しています。これにより、ソートが複数のスレッドで同時に行われ、並列性が向上します。ただし、プログラムが実際に並列化されるかどうかは、使用しているコンパイラや実行環境に依存します。
他にも、std::for_each, std::transform, std::reduceなどのアルゴリズムも並列処理が可能です。並列処理を活用するには、対象のアルゴリズムが並列処理に対応しているかどうか確認し、またコンパイラがサポートしているかどうかも確認する必要があります。
C++20はC++17からの進化として、言語仕様や標準ライブラリにいくつかの重要な変更があります。以下に、C++17からC++20への変更のいくつかについて簡単に説明します。ただし、これは簡略化された概要であり、全ての変更点を網羅していません。
C++20では、概念と呼ばれる新しい言語機能が導入されました。これは、テンプレート引数に対する制約を定義するためのもので、より直感的でエラーメッセージが分かりやすくなります。
template 
concept Integral = std::is_integral::value;
template 
void foo(T value) {
    // TはIntegralな型でなければならない
}
C++20では、範囲ベースのfor文で初期化子が後置されるようになりました。
std::vector numbers = {1, 2, 3, 4, 5};
for (int& num : numbers) {
    // ...
}
C++20では、コンセプトによる制約が緩和され、関数テンプレートの制約がより柔軟になりました。
template 
requires std::integral || std::floating_point
void bar(T value) {
    // Tは整数型または浮動小数点型でなければならない
}
C++20では、非同期プログラミングのためのコルーチンが導入されました。これにより、非同期なコードをより直感的に書くことができます。
generator generateNumbers() {
    for (int i = 0; i < 5; ++i) {
        co_yield i;
    }
}
int main() {
    for (int num : generateNumbers()) {
        std::cout << num << " ";
    }
    // 出力: 0 1 2 3 4
}
C++20では、新しいモジュールシステムが導入され、ヘッダーファイルの依存関係やコンパイル時間の削減が図られました。
// モジュールの宣言
module mymodule;
// モジュール内の宣言
export module mymodule;
export void myFunction();
これらはC++17からC++20への変更の一部です。変更点は多岐にわたり、様々な新機能や改善が含まれています。プログラマは特にこれらの変更に注意を払い、コードを最新の標準に合わせることが推奨されます。
2024年1月現在、策定中で発行前のC++23で予定されている追加変更の一部を紹介します。
C++20からC++23への移行において、以下の変更点に注意が必要です。
// C++20
for (auto i = size_t(0), s = v.size(); i < s; ++i)
// C++23
for (auto i = 0uz, s = v.size(); i < s; ++i)
// C++20
size_t s2 = std::max(1u, v.size()); // コンパイルエラー
// C++23
size_t s2 = std::max(1uz, v.size()); // OK
// C++20
// C++23
// C++20
std::cout << std::is_enum_v << '\n';    // true
// C++23
std::cout << std::is_scoped_enum_v << '\n';     // true
// C++20
std::cout << (s.find("++") != std::string::npos) << '\n'; // true
// C++23
std::cout << s.contains("++") << '\n'; // true
// C++20
auto a = std::to_underlying(State::Open); // c は char
// C++23
auto a = std::to_underlying(State::Open); // c は char
// C++23
std::ispanstream is{ input }; // span としてバッファを渡す
char buffer[30]{};
std::ospanstream os{ buffer }; // span としてバッファを渡す
// C++20
if constexpr (typeid(IShape) != typeid(Circle)) // エラー
// C++23
if constexpr (typeid(IShape) != typeid(Circle))
// C++23
std::stack s(v.begin(), v.end());
std::queue q(v.begin(), v.end());
これらの変更点は、まだ策定中の段階ですので正式な規格発行までに変更あるいは削除される可能性があります。
C++のキーワードは、言語の進化と共に変遷してきました。以下に、C++の主なバージョンごとに導入されたキーワードや変更点を示します。ただし、これは一般的な変遷であり、全ての変更点を網羅しているわけではありません。
bool: 論理型が導入されました。
explicit: 明示的な変換演算子やコンストラクタ呼び出しを制限するために使用されます。
mutable: メンバ関数がオブジェクトの状態を変更できることを示します。
auto: 変数の型推論を行います。
decltype: 式の型を取得します。
nullptr: ヌルポインタを表現するための新しいキーワード。
constexpr: 定数式の評価を宣言します。
overrideおよびfinal: 仮想関数のオーバーライドやクラスの最終派生を明示的に指定します。
C++11の拡張で新たなキーワードは導入されていませんでした。
inline変更: 関数や変数に対してinlineキーワードが変更され、関数内でのみ有効な意味から、同じ関数定義が複数のソースファイルに出現してもリンクエラーにならないことを示す意味に変更されました。
namespace: namespaceにインライン変数と関数が導入されました。
concept: テンプレートの制約を表現するための新しいキーワード。
requires: 関数テンプレートやクラステンプレートに対する要件を指定するために使用されます。
consteval: コンパイル時評価が必要な関数や変数に使用されます。
co_await, co_yield, co_return: コルーチン関連のキーワード。
import: モジュールシステムの導入。
現時点では確定されたキーワードはありませんが、言語やライブラリの進化に伴い、新しいキーワードが導入される可能性があります。
C++の標準は逐次的に拡張されており、各バージョンで新しい機能やキーワードが追加されています。最新のC++標準を確認し、新しいキーワードに対する理解を深めることが重要です。
C++は長い歴史を有するプログラミング言語であり、常に進化し続けています。未来への展望においては、C++は新しい要求や技術の変化に対応し、開発者が効率的かつ安全にコードを記述できるような機能や変更が期待されています。以下では、C++の将来的な方向性や提案中の機能や変更点について述べます。
モダンなプログラミング手法のサポート: C++はモダンなプログラミング手法に適応するために努力しています。将来的には、関数型プログラミングやマルチスレッドプログラミングなどの手法をサポートするための機能が追加されることが期待されます。これにより、開発者はより効果的に複雑なアプリケーションを構築できるようになるでしょう。
パフォーマンスの向上: C++は高いパフォーマンスが求められるアプリケーションの開発に広く使用されています。将来的には、ハードウェアの進化や最適化技術の導入により、C++の実行速度が向上することが期待されます。
標準ライブラリの拡充: 標準ライブラリの拡充は、新しいデータ構造やアルゴリズムの追加などを通じて、開発者にとっての利便性を向上させます。将来的なバージョンでは、これに焦点を当てた改善がなされるでしょう。
言語仕様のクリアリティと安全性の向上: C++は機能が豊富である一方で、複雑な言語仕様を抱えています。将来的には、言語仕様のクリアリティ向上と安全性の向上が焦点となり、開発者がコードをより理解しやすく、かつ安全に記述できるようになるでしょう。