1052 Stimmen

Was sind C++-Funktoren und ihre Verwendung?

Ich höre immer wieder viel über Funktoren in C++. Kann mir jemand einen Überblick darüber geben, was sie sind und in welchen Fällen sie nützlich sein könnten?

2voto

Martin Broadhurst Punkte 8923

Ein großer Vorteil der Implementierung von Funktionen als Funktoren ist, dass sie den Zustand zwischen Aufrufen beibehalten und wiederverwenden können. Zum Beispiel können viele dynamische Programmieralgorithmen, wie die Wagner-Fischer-Algorithmus zur Berechnung der Levenshtein-Abstand zwischen Zeichenketten, indem Sie eine große Ergebnistabelle ausfüllen. Es ist sehr ineffizient, diese Tabelle jedes Mal zuzuweisen, wenn die Funktion aufgerufen wird. Die Funktion als Funktor zu implementieren und die Tabelle zu einer Mitgliedsvariablen zu machen, kann die Leistung erheblich verbessern.

Nachstehend ein Beispiel für die Implementierung des Wagner-Fischer-Algorithmus als Funktor. Beachten Sie, wie die Tabelle im Konstruktor zugewiesen wird und dann in operator() mit Größenanpassung nach Bedarf.

#include <string>
#include <vector>
#include <algorithm>

template <typename T>
T min3(const T& a, const T& b, const T& c)
{
   return std::min(std::min(a, b), c);
}

class levenshtein_distance 
{
    mutable std::vector<std::vector<unsigned int> > matrix_;

public:
    explicit levenshtein_distance(size_t initial_size = 8)
        : matrix_(initial_size, std::vector<unsigned int>(initial_size))
    {
    }

    unsigned int operator()(const std::string& s, const std::string& t) const
    {
        const size_t m = s.size();
        const size_t n = t.size();
        // The distance between a string and the empty string is the string's length
        if (m == 0) {
            return n;
        }
        if (n == 0) {
            return m;
        }
        // Size the matrix as necessary
        if (matrix_.size() < m + 1) {
            matrix_.resize(m + 1, matrix_[0]);
        }
        if (matrix_[0].size() < n + 1) {
            for (auto& mat : matrix_) {
                mat.resize(n + 1);
            }
        }
        // The top row and left column are prefixes that can be reached by
        // insertions and deletions alone
        unsigned int i, j;
        for (i = 1;  i <= m; ++i) {
            matrix_[i][0] = i;
        }
        for (j = 1; j <= n; ++j) {
            matrix_[0][j] = j;
        }
        // Fill in the rest of the matrix
        for (j = 1; j <= n; ++j) {
            for (i = 1; i <= m; ++i) {
                unsigned int substitution_cost = s[i - 1] == t[j - 1] ? 0 : 1;
                matrix_[i][j] =
                    min3(matrix_[i - 1][j] + 1,                 // Deletion
                    matrix_[i][j - 1] + 1,                      // Insertion
                    matrix_[i - 1][j - 1] + substitution_cost); // Substitution
            }
        }
        return matrix_[m][n];
    }
};

1voto

Alex Punnen Punkte 3960

Um hinzuzufügen, habe ich Funktionsobjekte verwendet, um eine vorhandene Legacy-Methode an das Befehlsmuster anzupassen; (der einzige Ort, an dem die Schönheit des OO-Paradigmas wahr OCP fühlte ich ); Auch hier das Hinzufügen der entsprechenden Funktion Adapter Muster.

Angenommen, Ihre Methode hat die Signatur:

int CTask::ThreeParameterTask(int par1, int par2, int par3)

Wir werden sehen, wie wir es für das Command-Muster anpassen können - dazu müssen Sie zunächst einen Member-Funktionsadapter schreiben, damit er als Funktionsobjekt aufgerufen werden kann.

Hinweis - dies ist hässlich, und vielleicht können Sie die Boost bind Helfer usw. verwenden, aber wenn Sie nicht können oder wollen, ist dies eine Möglichkeit.

// a template class for converting a member function of the type int        function(int,int,int)
//to be called as a function object
template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
class mem_fun3_t
{
  public:
explicit mem_fun3_t(_Ret (_Class::*_Pm)(_arg1,_arg2,_arg3))
    :m_Ptr(_Pm) //okay here we store the member function pointer for later use
    {}

//this operator call comes from the bind method
_Ret operator()(_Class *_P, _arg1 arg1, _arg2 arg2, _arg3 arg3) const
{
    return ((_P->*m_Ptr)(arg1,arg2,arg3));
}
private:
_Ret (_Class::*m_Ptr)(_arg1,_arg2,_arg3);// method pointer signature
};

Außerdem brauchen wir eine Hilfsmethode mem_fun3 für die oben genannte Klasse, um den Aufruf zu erleichtern.

template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3> mem_fun3 ( _Ret (_Class::*_Pm)          (_arg1,_arg2,_arg3) )
{
  return (mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3>(_Pm));
}

Um nun die Parameter zu binden, müssen wir eine Bindefunktion schreiben. So, jetzt geht's los:

template<typename _Func,typename _Ptr,typename _arg1,typename _arg2,typename _arg3>
class binder3
{
public:
//This is the constructor that does the binding part
binder3(_Func fn,_Ptr ptr,_arg1 i,_arg2 j,_arg3 k)
    :m_ptr(ptr),m_fn(fn),m1(i),m2(j),m3(k){}

 //and this is the function object 
 void operator()() const
 {
        m_fn(m_ptr,m1,m2,m3);//that calls the operator
    }
private:
    _Ptr m_ptr;
    _Func m_fn;
    _arg1 m1; _arg2 m2; _arg3 m3;
};

Und eine Hilfsfunktion zur Verwendung der Klasse binder3 - bind3 :

//a helper function to call binder3
template <typename _Func, typename _P1,typename _arg1,typename _arg2,typename _arg3>
binder3<_Func, _P1, _arg1, _arg2, _arg3> bind3(_Func func, _P1 p1,_arg1 i,_arg2 j,_arg3 k)
{
    return binder3<_Func, _P1, _arg1, _arg2, _arg3> (func, p1,i,j,k);
}

Nun müssen wir dies mit der Klasse Command verwenden; verwenden Sie den folgenden Typedef:

typedef binder3<mem_fun3_t<int,T,int,int,int> ,T* ,int,int,int> F3;
//and change the signature of the ctor
//just to illustrate the usage with a method signature taking more than one parameter
explicit Command(T* pObj,F3* p_method,long timeout,const char* key,
long priority = PRIO_NORMAL ):
m_objptr(pObj),m_timeout(timeout),m_key(key),m_value(priority),method1(0),method0(0),
method(0)
{
    method3 = p_method;
}

So rufen Sie es auf:

F3 f3 = PluginThreadPool::bind3( PluginThreadPool::mem_fun3( 
      &CTask::ThreeParameterTask), task1,2122,23 );

Nota: f3(); ruft die Methode task1->ThreeParameterTask(21,22,23); .

Der vollständige Kontext dieses Musters ist im Folgenden dargestellt lien

1voto

Yantao Xie Punkte 11260

Functor kann auch verwendet werden, um die Definition einer lokalen Funktion innerhalb einer Funktion zu simulieren. Siehe dazu die question y eine andere .

Aber ein lokaler Funktor kann nicht auf externe Auto-Variablen zugreifen. Die Lambda-Funktion (C++11) ist eine bessere Lösung.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X