コンテンツにスキップ

C++/関数

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

関数の基本

[編集]

関数とは何か?

[編集]

関数は、プログラム内で特定のタスクを実行するための独立したコードブロックです。関数を使用することで、コードの再利用や保守が容易になり、プログラムの構造を論理的に分割することで可読性が向上します。

関数の定義と宣言

[編集]

関数の定義は、関数の具体的な動作を示す部分であり、宣言は関数の存在をコンパイラに知らせる部分です。C++11以降、トレイリングリターン型を使用した宣言は、特に複雑な型において可読性を高めます。

トレイリングリターン型による関数の宣言と定義

[編集]
// 関数の宣言
auto add(int a, int b) -> int;

// 関数の定義
auto add(int a, int b) -> int {
    return a + b;
}

旧式のC言語スタイル(非推奨)

[編集]

古いC言語スタイルの宣言は、モダンC++では推奨されません。

// 非推奨のスタイル
int add(int a, int b) {
    return a + b;
}

関数の呼び出しと戻り値

[編集]

関数は呼び出し元に制御を戻し、必要に応じて戻り値を返します。戻り値の型はトレイリングリターン型で明示すると分かりやすくなります。

auto result = add(3, 4);
std::cout << "結果: " << result << std::endl; // 結果: 7

パラメータと引数

[編集]

関数は入力としてパラメータを定義できます。呼び出し時に引数として渡されるデータを受け取り処理します。

// 関数の定義
auto printSum(int a, int b) -> void {
    auto sum = a + b;
    std::cout << "合計: " << sum << std::endl;
}

// 呼び出し例
printSum(5, 7); // 出力: 合計: 12

トレイリングリターン型の利点

[編集]
可読性向上
戻り値の型を関数名の後ろに記述することでコードの読みやすさが向上します。
一貫性
ラムダ式autoキーワードとスタイルを統一できます。
複雑な型の処理
テンプレート関数や特殊な型を使う場合に役立ちます。
まとめ

トレイリングリターン型を利用することで、コードの保守性や可読性を向上させることができます。旧来のC言語スタイルは避け、モダンC++の規約を取り入れることが推奨されます。

関数の種類

[編集]

戻り値のない関数

[編集]

戻り値のない関数は、結果を返さずに処理を実行する関数です。返り値の型として void を指定します。画面へのメッセージ表示やファイルへのデータ書き込みなどの副作用を目的とした処理によく使用されます。

auto printMessage() -> void {
    std::cout << "Hello, World!" << std::endl;
}

パラメータのない関数

[編集]

パラメータのない関数は、呼び出し時に引数を必要としない関数です。C++では空の引数リスト () で定義します(C23以前のCとは異なり、void の明示は不要です)。システムの状態取得など、外部入力に依存しない処理に適しています。

auto getCurrentTime() -> std::string {
    const auto now = std::time(nullptr);
    const auto local_time = *std::localtime(&now);
    std::ostringstream oss;
    oss << std::put_time(&local_time, "%Y-%m-%d %H:%M:%S");
    return oss.str();
}

戻り値とパラメータを持つ関数

[編集]

入力パラメータを受け取り、処理結果を戻り値として返す最も一般的な関数の形式です。入力データの変換や計算など、純粋な関数型プログラミングの基本となります。

auto add(const int a, const int b) -> int {
    return a + b;
}

auto getStringLength(const std::string& str) -> std::size_t {
    return str.length();
}

再帰関数

[編集]

関数内で自身を呼び出す関数です。問題を小さな部分問題に分割して解決する際に有効です。階乗計算やツリー構造の探索など、自己相似的な問題に適しています。ただし、深い再帰呼び出しはスタックオーバーフローのリスクがあるため、適切な終了条件の設定が重要です。

auto factorial(const int n) -> int {
    if (n <= 1) {
        return 1;
    }
    return n * factorial(n - 1);
}

これらの関数の種類を適切に使い分けることで、保守性と可読性の高いコードを設計できます。現代のC++では、トレイリングリターン型構文(auto f() -> return_type)を使用することで、特に複雑な戻り値の型を持つ関数や、テンプレート関数の定義が明確になります。

関数オーバーロード

[編集]

関数オーバーロードの概要

[編集]

関数オーバーロードとは、同じ名前で異なるパラメータリストを持つ複数の関数を定義する機能です。C++では、関数名が同じでもパラメータの数や型が異なれば、それぞれ独立した関数として扱われます。これにより、同じような操作を異なる型のデータに対して、統一的な名前で提供できます。

オーバーロード解決のルール

[編集]

コンパイラは以下の優先順位に従って最適な関数を選択します:

  1. 完全一致:パラメータの型と数が完全に一致する関数が最優先
  2. 昇格:整数の昇格(charからint)など、情報が失われない変換
  3. 標準変換:異なる数値型間の変換など
  4. ユーザー定義変換:変換コンストラクタやキャスト演算子による変換

複数の関数が同じ優先度で適合する場合は、曖昧性エラーとなります。

オーバーロードの例

[編集]
#include <iostream>
#include <string>

// 整数値を表示
auto print(const int num) -> void {
    std::cout << "Integer: " << num << std::endl;
}

// 浮動小数点数を表示
auto print(const double num) -> void {
    std::cout << "Double: " << num << std::endl;
}

// 文字列を表示
auto print(const std::string& str) -> void {
    std::cout << "String: " << str << std::endl;
}

auto main() -> int {
    print(42);        // Integer: 42
    print(3.14159);   // Double: 3.14159
    print("Hello");   // String: Hello(文字列リテラルから std::string への暗黙変換)
    return 0;
}

この例では、print関数が3つの異なるバージョンでオーバーロードされています:

  • 整数版(int
  • 浮動小数点版(double
  • 文字列版(const std::string&

コンパイラは呼び出し時の引数の型に基づいて、最適な関数を自動的に選択します。

オーバーロード時の注意点

[編集]
  • 戻り値の型の違いだけではオーバーロードできません
  • 参照修飾子(const、volatile)の有無はオーバーロードの区別に有効です
  • テンプレートと関数オーバーロードを併用する場合、特殊化の順序に注意が必要です
  • 暗黙の型変換による予期しない関数呼び出しを避けるため、必要に応じてexplicitキーワードを使用します

まとめ

[編集]

関数オーバーロードは、型安全かつ直感的なインターフェースを設計するための重要な機能です。現代のC++では、トレイリングリターン型構文(auto f() -> return_type)を使用することで、特にテンプレートや複雑な戻り値型を持つ関数のオーバーロードがより明確になります。

デフォルト引数

[編集]

デフォルト引数の概要

[編集]

デフォルト引数は、関数パラメータに初期値を設定する機能です。これにより、関数呼び出し時に一部の引数を省略でき、コードの簡潔性と柔軟性が向上します。

関数宣言でのデフォルト引数の基本構文:

auto function(int required, int optional = 10) -> void;

デフォルト引数の規則

[編集]
  1. デフォルト引数は右端のパラメータから順に指定する必要があります
  2. デフォルト値は関数宣言時に一度だけ指定します(定義時には記述しません)
  3. デフォルト値には定数式またはグローバル変数を使用可能です
  4. クラスメンバ関数では、thisポインタを使用したデフォルト値も設定可能です

基本的な使用例

[編集]
#include <iostream>
#include <string>

// デフォルト引数を持つ関数の宣言
auto greet(const std::string& name = "Guest") -> void {
    std::cout << "Hello, " << name << "!" << std::endl;
}

auto main() -> int {
    greet();           // 出力: "Hello, Guest!"
    greet("Alice");    // 出力: "Hello, Alice!"
    return 0;
}

複数のデフォルト引数

[編集]

関数は複数のデフォルト引数を持つことができます:

#include <iostream>
#include <string>

auto displayInfo(
    const std::string& name,
    const int age = 0,
    const std::string& city = "Unknown"
) -> void {
    std::cout << "Name: " << name 
              << ", Age: " << age 
              << ", City: " << city << std::endl;
}

auto main() -> int {
    displayInfo("Bob");                    // Name: Bob, Age: 0, City: Unknown
    displayInfo("Alice", 30);              // Name: Alice, Age: 30, City: Unknown
    displayInfo("Charlie", 25, "Tokyo");   // Name: Charlie, Age: 25, City: Tokyo
    return 0;
}

デフォルト引数とオーバーロード

[編集]

デフォルト引数は関数オーバーロードと組み合わせて使用する際に注意が必要です:

// 良い例:明確な区別がある
auto process(const int x) -> void;
auto process(const int x, const std::string& s = "") -> void;

// 悪い例:曖昧な呼び出しの可能性
auto process(const int x, const int y = 0) -> void;
auto process(const int x, const std::string& s = "") -> void;
// process(42); // エラー:どちらの関数を呼び出すべきか曖昧

ベストプラクティス

[編集]
  • デフォルト引数は関数宣言時にのみ指定し、定義では省略する
  • 必須パラメータを左側に、オプショナルなパラメータを右側に配置する
  • デフォルト値には意味のある値を使用する(例:数値の0、文字列の空文字列など)
  • オーバーロードとの組み合わせ時は、曖昧性を避ける
  • 変更される可能性のあるグローバル変数はデフォルト値として使用しない

まとめ

[編集]

デフォルト引数は、関数インターフェースを柔軟にし、コードの可読性を向上させる強力な機能です。ただし、適切な使用には規則とベストプラクティスの理解が重要です。関数オーバーロードと組み合わせる際は、特に注意が必要です。

参照渡しとポインタ渡し

[編集]

値渡しと参照渡しの基本

[編集]

C++では、関数への引数渡しに以下の3つの方法があります:

  1. 値渡し:引数の値がコピーされる
  2. 参照渡し:引数への直接アクセスが可能
  3. ポインタ渡し:引数のアドレスを通じたアクセス
#include <iostream>

// 値渡し:引数のコピーが作成される
auto passByValue(const int value) -> void {
    // valueはローカルコピー
    std::cout << "Value: " << value << std::endl;
}

// 参照渡し:元の変数への直接アクセス
auto passByReference(int& ref) -> void {
    ref = 100;  // 元の変数が変更される
}

// const参照:効率的な読み取り専用アクセス
auto passByConstReference(const int& ref) -> void {
    std::cout << "Read only: " << ref << std::endl;
}

auto main() -> int {
    int number{42};
    
    passByValue(number);         // 値のコピーが渡される
    passByReference(number);     // 変数への参照が渡される
    passByConstReference(number);// constな参照が渡される
    
    return 0;
}

参照渡しの特徴

[編集]
  • NULLを取り得ない(必ず有効なオブジェクトを参照)
  • 宣言時に初期化が必須
  • エイリアシング(別名)として機能
  • 参照先の変更不可
  • オーバーヘッドなし(通常、ポインタと同様に実装される)

ポインタ渡しの特徴

[編集]
#include <iostream>

auto passByPointer(int* ptr) -> void {
    if (ptr) {  // nullptr チェックが必要
        *ptr = 100;
    }
}

auto passByConstPointer(const int* ptr) -> void {
    if (ptr) {
        std::cout << "Value through pointer: " << *ptr << std::endl;
    }
}

auto main() -> int {
    int value{42};
    int* ptr{&value};
    
    passByPointer(ptr);           // ポインタを渡す
    passByPointer(nullptr);       // nullptr も渡せる
    passByConstPointer(ptr);      // const ポインタとして渡す
    
    return 0;
}

特徴:

  • nullptrを取り得る
  • ポインタ自体の再代入が可能
  • アドレス演算が可能
  • 明示的なアドレス操作が必要
  • nullptrチェックが必要

使い分けのガイドライン

[編集]

参照を使用する場合

[編集]
  • オブジェクトが必ず存在する場合
  • 単純な別名付けが目的の場合
  • 関数パラメータでの大きなオブジェクトの受け渡し
  • 演算子オーバーロード
  • 範囲for文での要素アクセス

ポインタを使用する場合

[編集]
  • nullptrが有効な値として必要な場合
  • 動的メモリ管理が必要な場合
  • ポインタ演算が必要な場合
  • 複数のオブジェクトを指す可能性がある場合
  • C言語との互換性が必要な場合

モダンC++でのベストプラクティス

[編集]
  • 基本的には参照を優先
  • 大きなオブジェクトはconst参照で渡す
  • スマートポインタ(std::unique_ptrstd::shared_ptr)の活用
  • 生ポインタの使用は最小限に
  • 所有権の移転にはstd::moveを使用

まとめ

[編集]

参照とポインタはそれぞれ異なる用途に適しており、状況に応じて適切な方法を選択することが重要です。モダンC++では、安全性の観点から参照の使用が推奨されますが、ポインタにも固有の利点があります。

ラムダ式と無名関数

[編集]

ラムダ式の概要

[編集]

ラムダ式は、匿名の関数(無名関数)を定義するための機能であり、関数オブジェクトとして扱われます。C++11で導入され、その後の標準で機能が拡張されています。関数をインラインで定義し、その場で使用できる特徴があり、簡潔で直感的な記法によってコードの可読性と保守性を向上させます。

ラムダ式の構文と基本的な使用法

[編集]

ラムダ式の基本構文は以下の通りです:

[ キャプチャリスト ] ( パラメータリスト ) specifier -> 戻り値型 { 本体 }

各要素の説明:

キャプチャリスト
ラムダ式が外部のスコープの変数を参照する方法を指定します。空の場合は外部変数を使用しないことを示します。
パラメータリスト
関数の引数リストを指定します。C++20からは自動的なテンプレートパラメータ推論もサポートされています。
specifier(省略可能)
constexpr、mutable、noexcept などの修飾子を指定できます。
戻り値型(省略可能)
ラムダ式が返す値の型です。省略時はコンパイラが自動的に推論します。
本体
実行される処理を記述します。
基本的な使用例:
#include <iostream>

auto main() -> int {
    // 基本的なラムダ式
    auto add = [](int x, int y) { return x + y; };
    std::cout << add(3, 5) << std::endl;  // 出力: 8

    // C++20: テンプレートパラメータを使用したラムダ式
    auto generic_add = []<typename T>(T x, T y) { return x + y; };
    std::cout << generic_add(3.14, 2.86) << std::endl;  // 出力: 6

    return 0;
}

キャプチャリスト

[編集]

キャプチャリストには以下の主要な種類があります:

[]
キャプチャなし。外部変数は使用できません。
[=]
デフォルトで値キャプチャ。外部変数をコピーして使用します。
[&]
デフォルトで参照キャプチャ。外部変数への参照を使用します。
[x, &y]
特定の変数のみをキャプチャ。xは値キャプチャ、yは参照キャプチャします。
[this]
メンバ関数内でthisポインタをキャプチャします。
[*this]
C++17以降、thisオブジェクトを値キャプチャします。
キャプチャの使用例:
#include <iostream>

auto main() -> int {
    int x{5};
    int y{10};

    // 参照キャプチャの例
    auto ref_lambda = [&x](){ x += 1; return x; };
    
    // 値キャプチャの例
    auto val_lambda = [=](){ return x + y; };
    
    // 特定の変数のみをキャプチャ
    auto mixed_lambda = [x, &y](){ y += x; return y; };

    std::cout << ref_lambda() << std::endl;  // 出力: 6
    std::cout << val_lambda() << std::endl;  // 出力: 15
    std::cout << mixed_lambda() << std::endl;  // 出力: 15

    return 0;
}

関数テンプレート

[編集]

テンプレートの概要

[編集]

関数テンプレートは、異なるデータ型や値に対して汎用的な処理を行うための機能です。テンプレートを使用することで、同じコードを異なるデータ型や値に対して再利用することができます。C++において、関数テンプレートは、ジェネリックプログラミングの手法の一部として広く利用されています。

テンプレート関数の定義と使用法

[編集]

テンプレート関数は、次のような構文を持ちます。

template <typename T>
T myFunction(T x, T y) {
    return x + y;
}

このように、templateキーワードとtypenameまたはclassキーワードを使用して、テンプレートパラメータを定義します。Tはテンプレートパラメータであり、任意のデータ型を指定することができます。関数内では、Tを通じてジェネリックな処理を行うことができます。

テンプレート関数を使用する際には、次のようにして呼び出します。

int result1 = myFunction(3, 5);         // int型の場合
double result2 = myFunction(3.5, 5.5); // double型の場合

引数として与えるデータ型に応じて、適切なテンプレートインスタンスが生成されます。

テンプレートの特殊化

[編集]

テンプレートの特殊化は、特定のデータ型に対してカスタム処理を提供するための機能です。一般的なテンプレート関数に加えて、特定のデータ型に対する特殊なバージョンを提供することができます。これにより、特殊なデータ型に対して最適化された処理を行うことができます。

// int型に特殊化されたテンプレート関数
template <>
int myFunction<int>(int x, int y) {
    return x * y;
}

上記の例では、int型に特殊化されたmyFunction関数が定義されています。この関数は、int型の引数に対して掛け算を行います。特殊化されたテンプレート関数は、通常のテンプレート関数と同様に使用されますが、特定のデータ型に対して優先的に選択されます。

関数と型推論

[編集]

C++は複雑な型システムを持ち、コードの安全性と可読性を向上させるために型推論の機能を提供しています。特に、関数の引数や戻り値、ラムダ式において型推論を活用することができます。C++11以降の規格で導入されたこれらの機能を利用することで、より簡潔でメンテナンスしやすいコードを書くことが可能です。

引数の型推論

[編集]

関数の引数に対する型推論は、C++20で導入されたコンセプトを使用することで実現できます。テンプレートを使用して、関数の引数の型を自動的に推論することができます。

#include <iostream>
#include <type_traits>

// C++20以降での型推論を使用した関数テンプレート
template<typename T>
auto printValue(T value) -> void {
    std::cout << value << std::endl;
}

auto main() -> int {
    printValue(42);        // int型を推論
    printValue(3.14);      // double型を推論
    printValue("Hello");   // const char*型を推論

    return 0;
}

この例では、printValue関数はテンプレートを使用して引数の型を推論します。テンプレートの引数Tが、自動的に渡された引数の型に置き換えられます。

戻り値の型推論

[編集]

戻り値の型推論は、C++14で導入された機能です。autoキーワードを使用して、関数の戻り値の型をコンパイラに推論させることができます。これにより、関数の宣言が簡潔になり、コードの可読性が向上します。

#include <iostream>

// 戻り値の型推論を使用した関数
auto add(int a, int b) -> auto {
    return a + b;
}

auto main() -> int {
    std::cout << add(5, 3) << std::endl;  // 8 と表示される

    return 0;
}

この例では、add関数の戻り値の型をautoとし、コンパイラに型推論を任せています。

ラムダ式の型推論

[編集]

ラムダ式は、C++11で導入された匿名関数の一種です。ラムダ式では、引数や戻り値の型を省略することができ、コンパイラが自動的に推論してくれます。ラムダ式の型推論を利用することで、コードがより簡潔になります。

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

auto main() -> int {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // ラムダ式を使用してベクトルの各要素を2倍にする
    std::for_each(numbers.begin(), numbers.end(), [](auto& n) {
        n *= 2;
    });

    for (const auto& n : numbers) {
        std::cout << n << " ";  // 2 4 6 8 10 と表示される
    }

    std::cout << std::endl;

    return 0;
}

この例では、ラムダ式の引数nの型をautoとしています。コンパイラはnの型を自動的に推論し、適切な型として扱います。また、戻り値も型推論で暗黙にintが推論されます。

まとめ

C++11以降の規格で導入された型推論の機能を活用することで、関数の引数や戻り値、ラムダ式の型を明示的に指定する必要がなくなり、コードがより簡潔で可読性の高いものになります。特に、C++14のauto戻り値型推論やC++20のコンセプトを使用することで、複雑な型を扱う場合でもシンプルなコードを書くことができます。これらの機能を積極的に利用することで、よりメンテナブルで効率的なプログラムを作成することができます。

型推論を用いるとテンプレートを使わずジェネリックプログラミングができる
型推論を使用することで、テンプレートを使わずにジェネリックプログラミングを行うことができます。これは、C++11以降で導入されたautoキーワードや、C++14以降で導入されたdecltypeキーワードなどを活用することで実現されます。

例えば、autoキーワードを使用することで、関数テンプレートを定義せずにジェネリックな関数を作成することができます。以下にその例を示します。

#include <iostream>

// ジェネリックな関数を定義
auto add(auto a, auto b) {
    return a + b;
}

auto main() -> int {
    auto result1 = add(5, 3);       // int型として推論される
    auto result2 = add(3.14, 2.5);  // double型として推論される

    std::cout << "Result 1: " << result1 << std::endl; // 8
    std::cout << "Result 2: " << result2 << std::endl; // 5.64

    return 0;
}

この例では、add関数はautoキーワードを使用して引数と戻り値の型を推論しています。そのため、関数がジェネリックになり、さまざまな型の引数を受け取ることができます。

同様に、decltypeキーワードを使用して変数の型を推論することもできます。以下にその例を示します。

#include <iostream>

auto main() -> int {
    int x{5};
    decltype(x) y = 10;  // 変数xと同じ型(int型)としてyの型が推論される

    std::cout << "Value of y: " << y << std::endl; // 10

    return 0;
}

この例では、decltype(x)を使用して変数yの型を変数xと同じ型として推論しています。このように、decltypeキーワードを使用することで、変数の型を他の式や変数から推論することができます。

これらの型推論機能を組み合わせて使うことで、テンプレートを使わずにジェネリックなプログラミングを行うことができます。これにより、コードがより簡潔で読みやすくなり、プログラムの保守性が向上します。

標準ライブラリの関数

[編集]

algorithm ライブラリの関数

[編集]

algorithm ライブラリには、STL(Standard Template Library)の一部として、さまざまな便利な関数が含まれています。主な関数のいくつかを以下に示します。

std::sort()
コンテナ内の要素をソートします。
std::find()
コンテナ内で指定された値を検索します。
std::count()
コンテナ内で指定された値の出現回数を数えます。
std::accumulate()
コンテナ内の要素を合計します。
std::transform()
コンテナ内の要素に対して変換を適用します。

functional ライブラリの関数

[編集]

functional ライブラリは、関数オブジェクトを操作するためのユーティリティ関数を提供します。主な関数のいくつかを以下に示します。

std::function
任意の関数や関数オブジェクトをラップし、関数型として使用するためのクラスです。
std::bind
関数の一部の引数を固定し、新しい関数を作成します。
std::plus, std::minus, std::multiplies, std::divides
二項演算を行う関数オブジェクトです。
std::greater, std::less
比較を行う関数オブジェクトです。

その他の標準ライブラリ関数の利用法

[編集]

その他の標準ライブラリ関数を利用するには、適切なヘッダファイルをインクルードし、関数を呼び出すだけです。例えば、std::sort関数を使用する場合、<algorithm>ヘッダをインクルードします。

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

auto main() -> int {
    std::vector<int> vec = {3, 1, 4, 1, 5, 9, 2, 6};

    // ソート
    std::sort(vec.begin(), vec.end());

    // 結果を表示
    for (const auto& elem : vec) {
        std::cout << elem << " ";
    }
    std::cout << std::endl;

    return 0;
}

このように、標準ライブラリ関数を利用することで、効率的で信頼性の高いコードを書くことができます。

C++11以降の関数関係の機能追加

[編集]

C++11以降では、関数に関する機能や新しいスタイルがいくつか導入されています。以下にいくつかの主な機能を紹介します。

ラムダ式(Lambda Expressions)
C++11では、ラムダ式が導入されました。これは、簡潔な匿名関数を定義するための構文です。ラムダ式は、関数オブジェクトを簡単に定義し、スコープ内で即座に使用することができます。
    auto func = [](auto x, auto y) { return x + y; };
    auto result = func(3, 4);  // 7
std::functionstd::bind
std::functionは、任意の呼び出し可能なターゲット(関数、関数オブジェクト、ラムダ式など)を保持するための汎用的なクラステンプレートです。std::bindは、関数やメンバ関数の呼び出しを部分的に適用するためのツールです。
    #include <functional>
    #include <iostream>

    void foo(int x, int y) {
        std::cout << "Sum: " << x + y << std::endl;
    }

    auto main() -> int {
        auto f = std::bind(foo, 10, std::placeholders::_1);
        f(20);  // Sum: 30

        return 0;
    }
右辺値参照と移動セマンティクス(Rvalue References and Move Semantics)
C++11では、右辺値参照と移動セマンティクスが導入されました。これにより、一時オブジェクトの所有権を効率的に移動できるようになり、効率的なメモリ管理やオブジェクトのパフォーマンスの向上が可能になりました。
    #include <iostream>
    #include <vector>

    auto main() -> int {
        std::vector<int> v1 = {1, 2, 3};
        std::vector<int> v2 = std::move(v1);  // v1の所有権がv2に移動される

        return 0;
    }
constexpr関数
constexpr修飾子を使って関数を宣言することで、コンパイル時に評価される定数式として使用できる関数を定義できます。これにより、コンパイル時の計算が可能になり、実行時のオーバーヘッドが軽減されます。
    constexpr int factorial(int n) {
        return n <= 1 ? 1 : n * factorial(n - 1);
    }

    auto main() -> int {
        constexpr int result = factorial(5);  // コンパイル時に評価される
        return 0;
    }
デフォルト関数と削除関数
= defaultおよび= deleteキーワードを使用して、デフォルトのコンストラクタやデストラクタを明示的に指定することができます。また、= deleteを使用して、意図的に特定の関数の定義を削除することもできます。
    class MyClass {
     public:
        // デフォルトのコンストラクタ
        MyClass() = default;

        // コピーコンストラクタを削除
        MyClass(const MyClass&) = delete;
    };
consteval関数
constevalはC++20で導入されたキーワードで、constexpr関数の一種ですが、コンパイル時に実行されることが保証される点で異なります。constevalを使用することで、コンパイル時に実行される関数を定義することができます。これにより、プログラムのパフォーマンスやセキュリティを向上させることができます。
以下は、constevalを使用して素数を判定する関数を定義する例です。
#include <iostream>

// トレイリングリターン型を使った素数判定関数
consteval auto is_prime(int n) -> bool {
    if (n <= 1) return false;
    if (n <= 3) return true;
    if (n % 2 == 0 || n % 3 == 0) return false;
    for (int i = 5; i * i <= n; i += 6) {
        if (n % i == 0 || n % (i + 2) == 0) return false;
    }
    return true;
}

auto main() -> int {
    constexpr auto num{17};

    if (constexpr bool result = is_prime(num); result) {
        std::cout << num << " is a prime number." << std::endl;
    } else {
        std::cout << num << " is not a prime number." << std::endl;
    }

    return 0;
}
この例では、is_prime関数がconstevalで修飾されています。そのため、この関数はコンパイル時に実行され、結果はコンパイル時に評価されます。constexpr修飾子を使用して変数numを定義し、この変数をis_prime関数の引数として使用しています。コンパイル時にis_prime関数が評価され、結果が定数式として求められます。その結果、変数resultはコンパイル時に確定され、実行時には不変の値となります。
このように、constevalを使用することで、コンパイル時に実行される関数を定義し、実行時のオーバーヘッドを避けることができます。これにより、効率的なコードを記述し、パフォーマンスの向上やセキュリティの確保を実現することができます。

これらの機能やスタイルの導入により、C++の関数や関数に関連するコードの記述がより簡潔で効率的になり、より安全で柔軟なプログラミングが可能になりました。

例題と演習

[編集]

簡単な問題から応用問題までの一連の演習

[編集]
簡単な問題
整数の配列が与えられたとき、その配列の要素の合計を計算する関数を作成してください。
中級レベルの問題
与えられた文字列が回文(前から読んでも後ろから読んでも同じ)かどうかを判定する関数を実装してください。
応用問題
フィボナッチ数列の第n項を計算する関数を再帰的に実装してください。ただし、この関数の計算時間が指数時間になることを避けるために、適切な方法で最適化してください。

回答例

[編集]
#include <iostream>
#include <cassert>

// 例題1の関数: 配列の合計を計算する関数
int sumArray(const int arr[], int size) {
    int sum = 0;
    for (int i = 0; i < size; ++i) {
        sum += arr[i];
    }
    return sum;
}

// 例題2の関数: 回文かどうかを判定する関数
bool isPalindrome(const std::string& str) {
    int left = 0;
    int right = str.length() - 1;

    while (left < right) {
        if (str[left] != str[right]) {
            return false;
        }
        ++left;
        --right;
    }
    return true;
}

// 例題3の関数: フィボナッチ数列の第n項を計算する関数
int fibonacci(int n) {
    if (n <= 1) {
        return n;
    }
    return fibonacci(n - 1) + fibonacci(n - 2);
}

auto main() -> int {
    // 例題1のテスト
    int arr[] = {1, 2, 3, 4, 5};
    assert(sumArray(arr, 5) == 15);

    // 例題2のテスト
    assert(isPalindrome("radar") == true);
    assert(isPalindrome("hello") == false);

    // 例題3のテスト
    assert(fibonacci(6) == 8);

    std::cout << "All tests passed!" << std::endl;
    return 0;
}

このプログラムでは、各関数をテストするために assert() マクロが使用されています。各関数が期待どおりに動作するかどうかを確認するために、適切な入力でテストを行います。