関数オブジェクト
C++ での関数オブジェクト > 性能

C++における関数オブジェクトの利点のひとつは、関数ポインタと異なり、コンパイラの最適化によってインライン化されやすいため、パフォーマンスが向上する可能性が高くなるという点である。たとえば、引数をインクリメントさせるシンプルな関数は関数オブジェクトとして実装できる:
struct IncrementFunctor {
  void operator()(int& i) { ++i; }
};
通常の関数:
void increment_function(int& i) { ++i; }
STL 関数 std::for_each() の実装は概ね下記のようなものである:
template<typename InputIterator, typename Function>
Function for_each(InputIterator first, InputIterator last, Function f) {
  for ( ; first != last; ++first)
    f(*first);
  return f;
}
ここに std::for_each() を適用すると下記のようになる:
int A[] = {1, 4, 2, 8, 5, 7};
const size_t N = sizeof(A) / sizeof(A[0]);
for_each(A, A + N, IncrementFunctor());
for_each(A, A + N, increment_function);
いずれの for_each()も期待通りの動作をするが、最初の方法では以下のように展開される。
IncrementFunctor for_each<int*, IncrementFunctor>(int*, int*, IncrementFunctor)
二番目の方法では以下のように展開される。
void(*)(int&) for_each<int*, void(*)(int&)>(int*, int*, void(*)(int&))
for_each<int*, IncrementFunctor>()の場合には関数が既知であるためコンパイラがインライン化できるが、for_each<int*, void(*)(int&)>() の場合にはコンパイル時に関数が不定でありインライン化できない。
現実には、コンパイラに指示すれば簡単に関数を既知にすることができる。コンパイラが関数の定義を認識しており、それがクラスの内外いずれでも同じように行われていさえすればよい。インライン化しない場合、リンカは関数がクラスの関数だとの指示さえあれば同じ関数の別のコンパイル単位の複数回の定義をエラーを生成せずに黙って見過ごす。リンカは同じ関数の定義がクラスの関数でない場合には複数回の定義を許容しないためである。
もうひとつの例として、std::sort()関数テンプレートを挙げる。
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <vector>
#include <chrono>

void createRandomNumbers(std::vector<int>& array, int seed) {
    std::srand(seed);
    for (auto&& x : array) { x = std::rand(); }
}

inline bool compareFunc(const int& a, const int& b) { return a < b; }

struct CompareFunctor {
    bool operator()(const int& a, const int& b) const { return a < b; }
};

template<typename TFunc> void performFunc(TFunc func) {
    const auto startTime = std::chrono::system_clock::now();
    func();
    const auto endTime = std::chrono::system_clock::now();
    std::cout << "Elapsed time [ms] = " << std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count() << std::endl;
}

int main() {
    std::vector<int> array(1000 * 1000);
    std::cout << "* std::qsort:" << std::endl;
    createRandomNumbers(array, 0);
    performFunc([&array]() {
        std::qsort(array.data(), array.size(), sizeof(int),
            [](const void* a, const void* b) { return *static_cast<const int*>(a) - *static_cast<const int*>(b); });
    });
    std::cout << "* std::sort and function pointer:" << std::endl;
    createRandomNumbers(array, 0);
    performFunc([&array]() {
        std::sort(array.begin(), array.end(), compareFunc);
    });
    std::cout << "* std::sort and functor:" << std::endl;
    createRandomNumbers(array, 0);
    performFunc([&array]() {
        std::sort(array.begin(), array.end(), CompareFunctor());
    });
    std::cout << "* std::sort and lambda:" << std::endl;
    createRandomNumbers(array, 0);
    performFunc([&array]() {
        std::sort(array.begin(), array.end(), [](const int& a, const int& b) { return a < b; });
    });
}
std::qsort()およびstd::sort()の実装はいずれもクイックソートとは限らず、処理系依存のため、直接性能を比較することに意味はないが、通例インライン展開が利用できるstd::sort()のほうが性能が良くなる。また、述語に関数ポインタを利用するよりも、関数オブジェクトを利用したほうが性能が良くなる。C++11で追加されたラムダ式は、コンパイラによって内部的には関数オブジェクトの定義に展開されるため、通例関数オブジェクトを利用した場合と同等の性能になる。