Menu
Coddy logo textTech

関数オブジェクトとラムダ式

CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 74/104。

多くのSTLアルゴリズムは、その動作をカスタマイズするための呼び出し可能オブジェクトを受け取ります。すでにstd::transformでラムダ式が使われているのを見たことがあるでしょう。C++で呼び出し可能オブジェクトを作成する2つの方法である、ファンクタラムダ式の両方について詳しく見ていきましょう。

ファンクタ(関数オブジェクト)は、operator()をオーバーロードするクラスであり、インスタンスを関数のように呼び出すことができます。

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

struct MultiplyBy {
    int factor;
    MultiplyBy(int f) : factor(f) {}
    
    int operator()(int x) const {
        return x * factor;
    }
};

int main() {
    std::vector<int> nums = {1, 2, 3, 4};
    std::vector<int> result(nums.size());
    
    std::transform(nums.begin(), nums.end(), result.begin(), MultiplyBy(3));
    // 結果: {3, 6, 9, 12}
}

ファンクタは呼び出しの間で状態を保持できますが、通常の関数ではそれができません。しかし、単純な操作のためにクラスを定義するのは冗長です。ラムダ式は、簡潔な代替手段を提供します:

int factor = 3;
std::transform(nums.begin(), nums.end(), result.begin(),
               [factor](int x) { return x * factor; });

ラムダ式の構文は [capture](parameters) { body } です。キャプチャ節は、ラムダがアクセスできる外部変数を指定します。すべてを値でキャプチャするには [=] を、すべてを参照でキャプチャするには [&] を使用するか、[factor][&factor] のように特定の変数をリストします。

ラムダは、カスタム基準によるソートのようなアルゴリズムでの1回限りの操作に特に便利です。

std::vector<int> nums = {5, -2, 8, -1};
std::sort(nums.begin(), nums.end(), 
          [](int a, int b) { return std::abs(a) < std::abs(b); });
// 絶対値でソート済み: {-1, -2, 5, 8}
challenge icon

チャレンジ

簡単

商品の価格にさまざまな割引戦略を適用するために、ファンクタとラムダ式の両方を使用した価格計算機を作成しましょう。

コードは2つのファイルに分けて構成します:

  • Discounts.h: ここで割引ファンクタとユーティリティ関数を定義します。

    割引率(整数)を保持する PercentageDiscount というファンクタを作成してください。その operator()double 型の価格を受け取り、割引後の価格を返す必要があります。例えば、100ドルの20%割引は80ドルを返します。

    差し引く固定額(double 型)を保持する FixedDiscount という別のファンクタを作成してください。その operator() は価格を受け取り、価格から固定額を引いた値を返す必要があります(ただし、0未満にはなりません)。

    const std::vector<double>& を受け取り、すべての価格をスペースで区切って表示し、最後に改行を出力する printPrices という関数を作成してください。各価格は小数点以下2桁でフォーマットしてください。

  • main.cpp: 5つの入力(それぞれ別の行)を読み取ります:
    1. 1つ目の商品の価格 (double)
    2. 2つ目の商品の価格 (double)
    3. 3つ目の商品の価格 (double)
    4. 適用する割引率 (整数、例:20%の場合は 20)
    5. 固定割引額 (double)

    3つの価格を含むベクトルを作成し、両方のアプローチを実演してください:

    1. Original prices: に続いて価格を表示します
    2. std::transformPercentageDiscount ファンクタを使用して、割引後の価格の新しいベクトルを作成します。After percentage discount: に続いて結果を表示します
    3. 元の価格に対して std::transformFixedDiscount ファンクタを使用して、別のベクトルを作成します。After fixed discount: に続いて結果を表示します
    4. 元の各価格を2倍にするラムダ式を指定して std::transform を使用します。Premium prices (doubled): に続いて結果を表示します
    5. ラムダ式を指定して std::sort を使用し、元の価格を降順にソートします。Sorted (high to low): に続いてソートされた価格を表示します

例えば、入力が 100.00, 50.00, 75.00, 20, 15.00 の場合:

Original prices: 100.00 50.00 75.00 
After percentage discount: 80.00 40.00 60.00 
After fixed discount: 85.00 35.00 60.00 
Premium prices (doubled): 200.00 100.00 150.00 
Sorted (high to low): 100.00 75.00 50.00 

このチャレンジでは、ファンクタ(割引額のような状態を保持する)とラムダ(迅速なインライン操作のために変数をキャプチャする)を比較できます。どちらのアプローチも、std::transformstd::sort のようなSTLアルゴリズムとシームレスに連携します。

チートシート

STLアルゴリズムは、動作をカスタマイズするために呼び出し可能オブジェクトを受け入れます。一般的な2つのアプローチは、ファンクタラムダ式です。

ファンクタとは、operator()をオーバーロードしたクラスのことで、インスタンスを関数のように呼び出すことができます。

struct MultiplyBy {
    int factor;
    MultiplyBy(int f) : factor(f) {}
    
    int operator()(int x) const {
        return x * factor;
    }
};

std::transform(nums.begin(), nums.end(), result.begin(), MultiplyBy(3));

ファンクタは呼び出し間で状態を保持できますが、冗長なクラス定義が必要です。

ラムダ式は、[capture](parameters) { body }という構文で、簡潔な代替手段を提供します。

int factor = 3;
std::transform(nums.begin(), nums.end(), result.begin(),
               [factor](int x) { return x * factor; });

キャプチャ節は、ラムダがアクセスできる外部変数を指定します。

  • [=] - すべてを値でキャプチャ
  • [&] - すべてを参照でキャプチャ
  • [factor] - 特定の変数を値でキャプチャ
  • [&factor] - 特定の変数を参照でキャプチャ

ラムダは、カスタムのソート基準を指定するために std::sort のようなアルゴリズムとうまく連携します。

std::sort(nums.begin(), nums.end(), 
          [](int a, int b) { return std::abs(a) < std::abs(b); });

自分で試してみよう

#include <iostream>
#include <vector>
#include <algorithm>
#include "Discounts.h"

int main() {
    // 入力を読み込む
    double price1, price2, price3;
    int percentageDiscount;
    double fixedDiscount;
    
    std::cin >> price1;
    std::cin >> price2;
    std::cin >> price3;
    std::cin >> percentageDiscount;
    std::cin >> fixedDiscount;
    
    // 3つの価格を含む vector を作成する
    std::vector<double> prices = {price1, price2, price3};
    
    // TODO: "Original prices:" を出力し、続けて printPrices を使用して価格を表示する
    
    // TODO: PercentageDiscount ファンクタを使用して std::transform を適用する
    // 結果を格納するための新しい vector を作成する
    // Print "After percentage discount:" を出力し、続けて結果を表示する
    
    // TODO: 元の価格に対して FixedDiscount ファンクタを使用して std::transform を適用する
    // 結果を格納するための新しい vector を作成する
    // Print "After fixed discount:" を出力し、続けて結果を表示する
    
    // TODO: 各元の価格を2倍にするラムダ式を使用して std::transform を適用する
    // 結果を格納するための新しい vector を作成する
    // Print "Premium prices (doubled):" を出力し、続けて結果を表示する
    
    // TODO: ラムダ式を使用して元の価格を降順にソートするために std::sort を適用する
    // Print "Sorted (high to low):" を出力し、続けてソートされた価格を表示する
    
    return 0;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン