読者です 読者をやめる 読者になる 読者になる

関数オブジェクトの存在意義

c/c++

まあそれにvirtualデストラクタがないから危険が危ない。
...
書いてて思ったのは、おそらくfunctorをポリモルフィックにしようとすると、結局ポインタ経由で使いたくなって、そうすると関数ぽく呼び出すときにデリファレンスしてやらなきゃいけなくなる。でもそれは現在の関数ポインタの挙動と不整合なので(デリファレンスしなきゃいけない関数ポインタなんてANSI以前のCみたいだ!)基本そういう使い方をしないように、unary_functionもpointer_to_unary_functionも仮想関数なしの設計なのかなと思った。でもそうするとunary_functionの存在意義が全く分からない。

std::unary_functionの存在意義がよくわからなくなった。 - *「ふっかつのじゅもんがちがいます。」withぬこ

C++における関数オブジェクトを渡して処理をさせるのは、
関数ポインタを渡し、処理させるのに似ている。
が、大きな違いが1つある。

クラスで定義された関数オブジェクトは、
オーバーロードされるoperator()を持つクラスであり、
このことにより、以下の3つの利点を得る。

  • アルゴリズムへ「コンパイル時」に関数オブジェクトを渡すことができる
  • 対応する呼び出しをインラインにすることで効率性を高める
  • 関数オブジェクトが使用する情報を局所的にカプセル化できる

関数ポインタでは、「実行時」にポインタが渡されるだけ。

関数ポインタでは、「実行時」に渡されたポインタをインラインにすることはできない。(故にCよりC++の方が高速になりえる)

関数ポインタでは、対応する操作を行う場合、
操作する対象として静的変数か、グローバル変数を用いなければならない。
(多くの場合、何かをするための汎用的な関数ポインタを宣言したとき、
 引数は1つ、ないしは2つなのだから、操作する対象、例えば配列、を
 指し示す何かを必要とするということ)

例えばstd::unary_functionの派生クラスとして、「環境への参照を持つ関数(イメージとしてはクロージャ)」を作ってこいつはデストラクタで何かをしないとダメだとして、unary_functionオブジェクトとしてコンテナにつっこんで何かした後に、オブジェクトをunary_functionとして破壊すると不定な動作になるというのはちょっと不安に思います。

そういう使い方は想定外なんでしょうか。

自分はC++で関数オブジェクトをコンテナに突っ込まなければならない、
というケースには遭遇したことがないです。


上記のように、関数オブジェクト(C++の関数風オブジェクト)の利点は、
コンパイル時に特性を決定できることであり、
(関数ポインタに比べた)効率性と局所的なカプセル化であり、

こんな感じで使っちゃうからです。

以下のソース参照のこと。
Widgetを持つコンテナから
ある値(何らかの意味を持つ値)の閾値に満たないものを後ろから探して返すコードです。

この場合、ファンクタクラスが、unary_functionを継承していなかった場合、
関数アダプタnot1を受け付けなくなってしまいます。

(unary_function, binary_functionは標準関数アダプタ用のtypedefを提供するというだけの機能を持つ)


もう1つ下は閾値を満たす終端からの最初の要素を得る関数アダプタなし版。


このあたりに存在意義があります。


関数オブジェクトや関数アダプタに関してはEffective STLなどに詳しいので是非。
codepadで実行

class Widget {
public:
    Widget(int value) : value_(value) {}
    int Value() const { return value_; }
private:
    int value_;
};

template<typename T>
class ValueThreshold : public unary_function<Widget, bool> {
private:
    const T threshold_;
public:
    ValueThreshold(const T& threshold) : threshold_(threshold) {}
    bool operator() (const Widget& widget) const {
        return widget.Value() >= threshold_;
    }
};

int main(int argc, char* argv[])
{
    Widget w1(500);
    Widget w2(30);
    Widget w3(2000);
    Widget w4(50);
    Widget w5(1000);
    
    list<Widget> widgets;
    
    widgets.push_back(w1);
    widgets.push_back(w2);
    widgets.push_back(w3);
    widgets.push_back(w4);
    widgets.push_back(w5);
    
    
    list<Widget>::reverse_iterator it = find_if(widgets.rbegin(), widgets.rend(),
        not1(ValueThreshold<int>(50)));
    if (it != widgets.rend()) {
        cout << (*it).Value() << endl;
    }

    list<Widget>::reverse_iterator it2 = find_if(widgets.rbegin(), widgets.rend(),
       ValueThreshold<int>(1500));
    if (it2 != widgets.rend()) {
        cout << (*it2).Value() << endl;
    }
    return 0;
}