関数オブジェクト | Programming Place Plus C++編【言語解説】 第34章

トップページC++編

C++編で扱っている C++ は 2003年に登場した C++03 という、とても古いバージョンのものです。C++ はその後、C++11 -> C++14 -> C++17 -> C++20 -> C++23 と更新されています。
なかでも C++11 での更新は非常に大きなものであり、これから C++ の学習を始めるのなら、C++11 よりも古いバージョンを対象にするべきではありません。特に事情がないなら、新しい C++ を学んでください。 当サイトでは、C++14 をベースにした新C++編を作成中です。

この章の概要

この章の概要です。


関数オブジェクト

関数オブジェクトとは、関数をオブジェクトにしたもののことを指します。なお、C++ では、関数オブジェクトのことをファンクタ (functor)と呼ぶこともあります。

関数がオブジェクトになっていることによって、関数は情報(データ)を持つことができる上に、変数に保存したり、他の関数の引数に渡したりできます。このような利点を持った上で、普通の関数と同様に「f()」のような形で呼び出せます。

変数に保存したり、他の関数に渡したりという用途でいえば、関数ポインタによる方法がありますが、関数ポインタは文字どおり、関数を指し示すポインタがあるだけですから、状態を表現できません。

プログラミング言語の種類によって、関数オブジェクトを実現する方法や考え方には差異がありますが、C++ では、関数呼び出しに使う()演算子をオーバーロードすることによって実現します。つまり、関数オブジェクト(たとえば obj)を用意して、obj() のように記述すれば、operator() が呼び出されるという寸法です。

実装例を挙げます。

#include <iostream>

class Counter {
public:
    Counter() : mCount(0)
    {}

    inline int operator()()
    {
        return mCount++;
    }

private:
    int mCount;
};

int main()
{
    Counter c;

    std::cout << c() << std::endl;
    std::cout << c() << std::endl;
    std::cout << c() << std::endl;
}

実行結果:

0
1
2

Counterクラスは、operator() の呼び出し回数を記録します。「c()」のように呼び出しを行うたびに、現在のカウンタの値を返しつつ、インクリメントを行っています。「c」を関数名のように見れば、普通の関数の呼び出しですが、必要な情報をしっかり自己管理していることが分かります。

STLアルゴリズム

関数オブジェクトを活用する例として最たるものが、STLアルゴリズムです。STLアルゴリズムは、コンテナ、イテレータと並び、STL を構成する基盤要素の1つです。

STLアルゴリズムについての詳細は、【標準ライブラリ】編で扱っていますので、そちらを参照してください。STLアルゴリズムに含まれている各関数については、第18章第24章で、関連する話題について、第25章第26章で扱っています。


メンバポインタ

STLアルゴリズムの多くは、関数や関数オブジェクトを引き渡すことで、動作を指定できるようになっています。場合によっては、メンバ関数を使用したいこともあるかもしれませんが、その場合には、以下のようにしてメンバ関数を指すポインタを取得する必要があります(staticメンバ関数の場合は、通常の関数と同じ扱いになるので、通常の関数ポインタが使えます)。

class MyClass {
public:
    void Func() {}
};

void (MyClass::*pFunc)() = &MyClass::Func;

通常の関数ポインタと違い、型名にも、取得する際にも「どのクラスの」という部分の指定が必要です。こうして得られるポインタは、「メンバへのポインタ」や単に「メンバポインタ」などと呼ばれることがあります。

ここから少し分かりづらい部分です。クラスは1つですが、そこから作られるオブジェクトは複数あり得る訳ですが、メンバポインタを取得する際に、オブジェクトの指定をしていません。そのため実際に、メンバポインタを経由して、メンバ関数を呼び出す際には、「どのオブジェクトから」の指定が必要になります。

#include <iostream>

class MyClass {
public:
    void Func()
    {
        std::cout << "call Func()" << std::endl;
    }
};

int main()
{
    void (MyClass::*pFunc)() = &MyClass::Func;

    MyClass c;
    (c.*pFunc)();   // メンバポインタを経由して、c のメンバ関数を呼び出す

    MyClass* p = &c;
    (p->*pFunc)();  // メンバポインタを経由して、p が指す先にあるオブジェクト c のメンバ関数を呼び出す
}

実行結果:

call Func()
call Func()

妙な構文ですが、それぞれ、「.*」と「->*」という演算子を用いて、メンバ関数の呼び出しを行っています。

同様のことが、メンバ変数に対しても行えます。

#include <iostream>

struct MyStruct {
    int value;
};


int main()
{
    int MyStruct::* pValue = &MyStruct::value;

    MyStruct c;
    c.*pValue = 10;   // メンバポインタを経由して、c のメンバ変数へ代入する
    std::cout << c.value << std::endl;

    MyStruct* p = &c;
    p->*pValue = 20;  // メンバポインタを経由して、p が指す先にあるオブジェクト c のメンバ変数へ代入する
    std::cout << c.value << std::endl;
}

実行結果:

10
20


練習問題

問題① 本章の冒頭で取り上げた関数オブジェクトの例を改造して、初期値や増分値を自由に変更できるようにしてください。

問題② 問題①をさらに改造して、Counter をクラステンプレートにしてください。


解答ページはこちら

参考リンク


更新履歴

’2018/7/13 サイト全体で表記を統一(「静的メンバ」–>「staticメンバ」)

’2018/4/5 VisualStudio 2013 の対応終了。

’2018/4/2 「VisualC++」という表現を「VisualStudio」に統一。

≪さらに古い更新履歴を展開する≫



前の章へ (第33章 メンバ関数テンプレート)

次の章へ (第35章 非メンバの演算子オーバーロード)

C++編のトップページへ

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る