コンテンツにスキップ

C++/CからC++への移行

出典: フリー教科書『ウィキブックス(Wikibooks)』
< C++

はじめに

[編集]

C++は、C言語の強力な基盤の上にオブジェクト指向プログラミングやその他の高レベルな機能を追加した言語です。特にモダンC++(C++11以降の標準)は、コードの安全性、効率性、生産性を向上させる数多くの新機能を提供しています。この章では、C言語のプログラマがC++へ移行する際に役立つ情報を提供し、C++の利点とその新機能を紹介します。

基本的な違い

[編集]

C++では、プログラムの構造化や名前の衝突を避けるために、名前空間が導入されています。C++の標準ライブラリは、すべてstdという名前空間に含まれています。

#include <iostream>

namespace MyNamespace {
    void hello() {
        std::cout << "Hello from MyNamespace!" << std::endl;
    }
}

auto main() -> int {
    MyNamespace::hello();
    return 0;
}

この例では、MyNamespaceという名前空間を定義し、その中にhello関数を配置しています。これにより、名前の衝突を避けることができます。

プログラミングスタイルの変化

[編集]

C++はオブジェクト指向プログラミング(OOP)をサポートし、データと関数をクラスという単位でまとめることができます。これにより、コードの再利用性や保守性が向上します。

class MyClass {
  public:
    void sayHello() {
        std::cout << "Hello, World!" << std::endl;
    }
};

auto main() -> int {
    MyClass obj;
    obj.sayHello();
    return 0;
}

この例では、MyClassというクラスを定義し、その中にsayHelloというメンバ関数を持たせています。main関数では、MyClassのインスタンスobjを作成し、sayHelloを呼び出しています。

メモリ管理とスマートポインタ

[編集]

C言語では、mallocfreeを使って手動でメモリ管理を行いますが、C++ではnewdeleteを使って動的メモリを管理します。さらに、C++11以降ではスマートポインタが導入され、メモリ管理が簡単かつ安全になりました。

#include <memory>

void useSmartPointer() {
    std::unique_ptr<int> p1(new int(5));
    std::shared_ptr<int> p2 = std::make_shared<int>(10);
    // メモリは自動的に解放される
}

std::unique_ptrは単一の所有権を持つポインタで、std::shared_ptrは複数の所有権を共有するポインタです。これにより、メモリリークやダングリングポインタのリスクが減少します。

標準ライブラリの利用

[編集]

C++の標準ライブラリ(STL: Standard Template Library)は、データ構造やアルゴリズムを提供します。これにより、効率的で再利用可能なコードを簡単に書くことができます。

#include <vector>
#include <algorithm>
#include <iostream>

auto main() -> int {
    std::vector<int> vec = {1, 5, 6, 4, 3};
    std::sort(vec.begin(), vec.end());
    for (int n : vec) {
        std::cout << n << " ";
    }
    return 0;
}

この例では、std::vectorを使って整数のリストを作成し、std::sortでソートしています。

関数の強化

[編集]

C++では、関数のオーバーロードやデフォルト引数がサポートされており、同じ名前の関数を異なる引数リストで定義できます。また、C++11以降ではラムダ式が導入され、簡潔に関数を定義できます。

#include <iostream>
#include <functional>

void print(int x) {
    std::cout << "int: " << x << std::endl;
}

void print(double x) {
    std::cout << "double: " << x << std::endl;
}

auto main() -> int {
    print(10);       // int: 10
    print(10.5);     // double: 10.5

    auto lambda = [](int x) { return x * 2; };
    std::cout << lambda(5) << std::endl;  // 10
    return 0;
}

型安全と型推論

[編集]

C++では型安全性が重要視されており、autoキーワードを使ってコンパイラに型を推論させることができます。また、decltypeを使って式の型を取得できます。

#include <iostream>

auto main() -> int {
    auto x = 5;        // int
    auto y = 5.5;      // double
    decltype(x) z = 10; // int

    std::cout << x << ", " << y << ", " << z << std::endl;
    return 0;
}

モダンC++の機能

[編集]

モダンC++の特徴的な機能として、ムーブセマンティクスと右辺値参照があります。これにより、リソースの効率的な管理が可能となります。

#include <iostream>
#include <vector>

void processVector(std::vector<int>&& vec) {
    std::cout << "Processing vector of size " << vec.size() << std::endl;
}

auto main() -> int {
    std::vector<int> myVec = {1, 2, 3, 4, 5};
    processVector(std::move(myVec));  // ムーブセマンティクスを利用
    return 0;
}

この例では、std::moveを使ってmyVecを右辺値参照として渡し、リソースを効率的に移動しています。

例外処理

[編集]

C++では例外処理を利用して、エラーが発生した際にプログラムの安全な終了を図ることができます。

#include <iostream>
#include <stdexcept>

void mayThrow(bool shouldThrow) {
    if (shouldThrow) {
        throw std::runtime_error("An error occurred!");
    }
}

auto main() -> int {
    try {
        mayThrow(true);
    } catch (const std::runtime_error& e) {
        std::cerr << "Caught an exception: " << e.what() << std::endl;
    }
    return 0;
}

この例では、mayThrow関数がstd::runtime_errorを投げ、それをtry-catchブロックで捕捉しています。

コンパイル時のプログラム

[編集]

テンプレートプログラミングにより、型に依存しない汎用的な関数やクラスを定義できます。また、constexprを使ってコンパイル時に評価される定数式を定義できます。

#include <iostream>

template <typename T>
T add(T a, T b) {
    return a + b;
}

constexpr int factorial(int n) {
    return (n <= 1) ? 1 : (n * factorial(n - 1));
}

auto main() -> int {
    std::cout << "Sum: " << add(5, 3) << std::endl;  // Sum: 8
    std::cout << "Factorial of 5: " << factorial(5) << std::endl;  // Factorial of 5: 120
    return 0;
}

C++11以降の新機能

[編集]

C++11以降、標準ライブラリや言語仕様に多くの新機能が追加されました。これには、ラムダ式、スマートポインタ、非同期処理、コンパイル時の評価などが含まれます。

C++14以降の機能

[編集]

C++14では、いくつかの改善が行われました。特に、ラムダ式の機能が強化され、より柔軟に使えるようになりました。また、コンパイル時の最適化が強化され、コードの効率が向上しました。

ラムダ式の強化

[編集]

C++14では、ラムダ式の捕捉リストにdecltypeを使用することで、引数の型を動的に推論できます。また、ラムダ式で返される型を自動的に推論させることができます。

#include <iostream>

auto main() -> int {
    int x = 5;
    auto lambda = [x](int y) { return x + y; }; // ラムダ式の引数に型を推論
    std::cout << lambda(3) << std::endl;  // 8
    return 0;
}

この例では、ラムダ式内でキャプチャされる変数xが、型推論によって自動的に適切に取り扱われます。

型推論の強化

[編集]

C++14では、型推論が強化され、変数宣言の際にautoをより柔軟に使用できるようになりました。また、C++14からはautoを使って戻り値の型を推論することができ、より簡潔なコードが書けます。

#include <iostream>

auto add(int a, int b) {
    return a + b; // 戻り値の型は自動的にintとして推論される
}

auto main() -> int {
    std::cout << add(5, 3) << std::endl;  // 8
    return 0;
}

C++14では、関数の戻り値の型もautoを使って自動的に推論することができます。

C++17の新機能

[編集]

C++17では、さらに多くの新機能が追加され、C++の生産性とパフォーマンスが向上しました。特に、構造化束縛、インライン変数、std::optionalstd::variantなどの新しいデータ型が注目されます。

構造化束縛

[編集]

構造化束縛を使用すると、タプルやペアの要素を簡単に変数に展開できます。これにより、複雑なデータ構造を扱う際のコードがシンプルになります。

#include <iostream>
#include <tuple>

auto main() -> int {
    std::tuple<int, double, std::string> t = {1, 3.14, "Hello"};
    auto [x, y, z] = t;  // 構造化束縛でタプルの要素を展開

    std::cout << x << ", " << y << ", " << z << std::endl;  // 1, 3.14, Hello
    return 0;
}

この例では、std::tupleの要素を[x, y, z]のように簡単に展開しています。

インライン変数

[編集]

C++17では、インライン変数が導入されました。これにより、定数やグローバル変数をconstexprと同様にインラインで定義できるようになりました。

#include <iostream>

inline int global_value = 42; // インライン変数

auto main() -> int {
    std::cout << global_value << std::endl;  // 42
    return 0;
}

このインライン変数は、複数のソースファイルにわたる定義を簡潔に扱うことができます。

std::optional と std::variant

[編集]

C++17では、std::optionalstd::variantが追加され、値が存在しない場合や、複数の異なる型を持つ可能性のある値を扱うのが簡単になりました。

#include <iostream>
#include <optional>

auto main() -> int {
    std::optional<int> val = 10;
    if (val) {
        std::cout << *val << std::endl;  // 10
    } else {
        std::cout << "No value" << std::endl;
    }
    return 0;
}

std::optionalは値が存在するかどうかを簡単にチェックでき、std::variantは複数の型を1つの変数で保持することができます。

C++20の新機能

[編集]

C++20では、さらに多くの新機能が追加され、特にコンセプト、コルーチン、std::rangesライブラリなど、プログラミングの抽象化や生産性を向上させるツールが提供されています。

コンセプト

[編集]

コンセプトは、テンプレートパラメータに対する制約を指定する方法を提供します。これにより、コンパイラエラーが発生する前にテンプレートの使用方法が確認でき、コードの安全性が向上します。

#include <iostream>

template<typename T>
concept Incrementable = requires(T x) {
    { x++ } -> std::same_as<T&>;
};

template<Incrementable T>
T increment(T x) {
    return x++;
}

auto main() -> int {
    std::cout << increment(10) << std::endl;  // 11
    return 0;
}

この例では、Incrementableというコンセプトを定義し、テンプレート関数にその制約を適用しています。

コルーチン

[編集]

C++20ではコルーチンが導入され、非同期プログラミングがより直感的に書けるようになりました。コルーチンを使うことで、非同期処理を同期的に記述できます。

#include <iostream>
#include <coroutine>

std::coroutine_handle<> coroutine_example() {
    std::cout << "Start coroutine" << std::endl;
    co_return;
}

auto main() -> int {
    coroutine_example();
    return 0;
}

この例では、簡単なコルーチンを使用して非同期処理の流れを示しています。

std::ranges ライブラリ

[編集]

C++20では、std::rangesライブラリが追加され、範囲(range)に対する操作が簡潔に記述できるようになりました。これにより、データ構造やアルゴリズムの操作が直感的になります。

#include <iostream>
#include <ranges>
#include <vector>

auto main() -> int {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    auto result = vec | std::ranges::transform([](int x) { return x * 2; });
    for (auto x : result) {
        std::cout << x << " ";
    }
    return 0;
}

この例では、std::ranges::transformを使って、ベクター内のすべての要素を2倍にしています。

まとめ

[編集]

C++は、強力で柔軟なプログラミング言語であり、モダンC++(C++11以降)の新機能を使うことで、コードの効率、可読性、安全性が向上します。新しい機能を積極的に活用することで、より高品質なプログラムを作成できます。