【C++】色々な関数オブジェクト【ラムダ ファンクタ 関数ポインタ】


C++では関数をオブジェクトとして扱う事ができます。関数を変数に格納したり、引数として渡したり、テンプレートに渡したりすることも可能になります。

C++では色々な方法でこの関数オブジェクト(function object)を扱うことができます。

最近は# ラムダ式が頻繁に用いられています。C++で古くから用いられている関数オブジェクトとしては# ファンクタが有名です。

スポンサーリンク

ラムダ式

ラムダ式(lambda expression)は関数オブジェクトを簡易的に定義するための機能です。プログラム中の様々なタイミングで定義が可能です。

ラムダ式はautoで保持することができます。

auto fn = [](int v) { return v * 2; };
fn(3); // 6

無名関数の即時実行やクロージャーを実現することもできます。

int i = 0;
[&] { i = 9; }();
std::cout << i; // 9

int j = 0;
auto fn = [&j] { return j++; };
std::cout << fn(); // 0
std::cout << fn(); // 1

ラムダ式はアルゴリズム系の標準ライブラリでも活用できます。

std::vector<int> v = {1, 2, 3, 4};
std::for_each(v.begin(), v.end(), [](int i) {
  std::cout << i;
}); // 1234

ラムダ式の評価結果であるクロージャーオブジェクト(closure object)は関数ポインタへの変換機能を持つため、C言語APIの比較関数やコールバック関数として活用することもできます。

char a[] = {'c', 'b', 'a'};
qsort(a, sizeof a, sizeof *a, [](const void *l, const void *r) {
  return *(char*)l - *(char*)r;
});
a; // {'a', 'b', 'c'}

関数オブジェクトを引数としてやり取りする際には、テンプレートや# std::functionを活用する必要があります。より詳しい説明は以下のページを参考にしてください。

>> 関数を引数に渡す方法

スポンサーリンク

ファンクタ

クラスのoperator()演算子を多重定義/オーバロードすることで、クラスのインスタンスに対して関数と同等の振る舞いをさせることができます。このような関数オブジェクトはファンクタ(functor)と呼ばれています。

struct Functor {
  int operator()(int v) { return v * 2; } // 関数呼び出し演算子を定義
};
Functor fn = Functor();
fn(3); // 6 (関数呼び出しが可能)

メンバ変数を活用すれば、関数に状態を持たせることも可能になります。

struct Functor {
  int i;
  Functor(int i) : i(i) {}
  int operator()(int v) { return v * i++; }
};
Functor fn = Functor(1);
fn(3); // 3
fn(3); // 6
fn(3); // 9
実は、ラムダ式のキャプチャ機能は、このファンクタとメンバ変数の仕組みを応用した形で実現されています。

ファンクタはラムダ式と同様に、標準ライブラリとともに活用することができます。

struct Print {
  void operator()(int i) { std::cout << i; }
};
std::vector<int> v = {1, 2, 3, 4};
std::for_each(v.begin(), v.end(), Print()); // 1234

一昔前まではC++の関数オブジェクトと言えばこのファンクタのことを指していました。

関数ポインタ

関数ポインタ(function pointer)は関数へのポインタです。関数ポインタは厳密には関数オブジェクトではないのですが、用途によっては関数オブジェクトと同等に扱うこともできるため、注意点とともに紹介しておきます。

int function(int v) { return v * 2; }
int(*fn)(int) = function;
fn(3); // 6

標準ライブラリは関数ポインタにも対応しています。

const char* a[] = {"hello", "world"};
std::for_each(std::begin(a), std::end(a), puts); // hello world

C言語との連携を意識する際には関数オブジェクトの代わりにこちらの関数ポインタを用いたAPI設計を採ると良いでしょう。

関数の定義方法にもよりますが、関数ポインタによる関数のやり取りは、コンパイラ側の最適化の恩恵を受けられなくなる場合がありますので、基本的には先程紹介したラムダ式やファンクタを用いるがベストです。

ブロック

ブロック(Blocks)はApple製コンパイラ(Apple LLVM)のC言語拡張です。利用用途はラムダ式とほぼ同じです。

int (^fn)(int) = ^(int v) { return v * 2; };
fn(3); // 6

クロージャーとしての利用も可能です。

__block const char* s = NULL;
^{ s = "hello"; }();
puts(s); // "hello"

やはり標準ライブラリとのやり取りが可能です。

std::vector<int> v = {1, 2, 3, 4};
std::for_each(v.begin(), v.end(), ^(int i) {
    std::cout << i;
}); // 1234

std::function

関数オブジェクトをstd::functionクラスで保持することも可能です。

std::function<int(int)> fn = [](int v) { return v * 2; };
fn(3);    // 6

std::function<int(int)> gn = isspace;
gn('\n'); // 1 (true)

引数型として利用すれば、関数オブジェクトを関数間でやり取りすることが可能になります。

std::function<int(int)> get() { return [](int v) { return v * 2; }; }
int call(std::function<int(int)> fn) { return fn(3); }
int v = call(get()); // 6

std::functionはラムダ式だけでなく、先程の# ファンクタ# 関数ポインタ# ブロックを保持することも可能です。

std::function<int(int)> functor = Functor();
functor(3); // 6

std::function<int(int)> funcPtr = function;
funcPtr(3); // 6

std::bind

std::bindで関数オブジェクトや関数ポインタをラップする方法もあります。

auto fn = std::bind([](int v) { return v * 2; }, std::placeholders::_1);
fn(3); // 6

std::placeholders::_1は第一引数の存在を示しています。第二引数が必要な場合は続けてstd::placeholders::_2を渡します。

bind関数は非推奨となったbind1st/bind2nd関数の代わりとして使うことができます。

ラップされたオブジェクトをstd::functionに変換することも可能です。

std::function<int(int)> fn = std::bind([](int v) { return v * 2; }, std::placeholders::_1);
fn(3); // 6

ファンクタや関数ポインタを保持することも可能です。これによって部分適用の実現が可能となります。

auto fn_Functor = std::bind(Functor(), std::placeholders::_1);
fn_Functor(3); // 6

auto fn_FuncPtr = std::bind(function, std::placeholders::_1);
fn_FuncPtr(3); // 6

std::bind構築時、事前に引数を与えることも可能です。これによって遅延評価の実現が可能となります。

auto fn = std::bind(isdigit, '9');
fn(); // 1 (true)

auto fn = std::bind([](int v) { return v * 2; }, 3);
fn(); // 6

auto fn = std::bind([](auto& f) { return 1 + f(); }, []() { return 2; });
fn(); // 3

広告

関連するオススメの記事