Programing

C ++ 펑터와 그 용도는 무엇입니까?

lottogame 2020. 9. 29. 07:09
반응형

C ++ 펑터와 그 용도는 무엇입니까?


C ++의 펑터에 대해 계속 많이 듣습니다. 누군가 나에게 그들이 무엇인지, 어떤 경우에 유용 할 것인지에 대한 개요를 줄 수 있습니까?


functor는 operator ()를 정의하는 클래스 일뿐입니다. 이를 통해 함수와 "같이 보이는"객체를 만들 수 있습니다.

// this is a functor
struct add_x {
  add_x(int x) : x(x) {}
  int operator()(int y) const { return x + y; }

private:
  int x;
};

// Now you can use it like this:
add_x add42(42); // create an instance of the functor class
int i = add42(8); // and "call" it
assert(i == 50); // and it added 42 to its argument

std::vector<int> in; // assume this contains a bunch of values)
std::vector<int> out(in.size());
// Pass a functor to std::transform, which calls the functor on every element 
// in the input sequence, and stores the result to the output sequence
std::transform(in.begin(), in.end(), out.begin(), add_x(1)); 
assert(out[i] == in[i] + 1); // for all i

펑터에 대한 몇 가지 좋은 점이 있습니다. 하나는 일반 함수와 달리 상태를 포함 할 수 있다는 것입니다. 위의 예는 무엇을 제공하든 42를 더하는 함수를 만듭니다. 하지만 그 값 42는 하드 코딩되지 않았고, functor 인스턴스를 만들 때 생성자 인자로 지정되었습니다. 다른 값으로 생성자를 호출하여 27을 더한 또 다른 가산기를 만들 수 있습니다. 이를 통해 멋지게 사용자 정의 할 수 있습니다.

마지막 줄에서 볼 수 있듯이 std :: transform 또는 기타 표준 라이브러리 알고리즘과 같은 다른 함수에 대한 인수로 functor를 종종 전달합니다. 위에서 말했듯이 펑 터는 상태를 포함하기 때문에 "사용자 정의"할 수 있다는 점을 제외하면 일반 함수 포인터로도 똑같이 할 수 있습니다 (함수 포인터를 사용하려면 함수를 작성해야합니다). 인수에 정확히 1을 추가했습니다. functor는 일반적이며 초기화 한 모든 항목을 추가합니다.) 그리고 잠재적으로 더 효율적입니다. 위의 예에서 컴파일러 std::transform는 호출해야하는 함수를 정확히 알고 있습니다. 을 호출해야합니다 add_x::operator(). 즉, 해당 함수 호출을 인라인 할 수 있습니다. 그리고 그것은 벡터의 각 값에 대해 수동으로 함수를 호출 한 것처럼 효율적입니다.

대신 함수 포인터를 전달한 경우 컴파일러는 자신이 가리키는 함수를 즉시 볼 수 없으므로 상당히 복잡한 전역 최적화를 수행하지 않는 한 런타임에 포인터를 역 참조한 다음 호출해야합니다.


약간의 추가. boost::function,를 사용하여 다음과 같이 함수와 메서드에서 펑터를 만들 수 있습니다 .

class Foo
{
public:
    void operator () (int i) { printf("Foo %d", i); }
};
void Bar(int i) { printf("Bar %d", i); }
Foo foo;
boost::function<void (int)> f(foo);//wrap functor
f(1);//prints "Foo 1"
boost::function<void (int)> b(&Bar);//wrap normal function
b(1);//prints "Bar 1"

boost :: bind를 사용하여이 펑터에 상태를 추가 할 수 있습니다.

boost::function<void ()> f1 = boost::bind(foo, 2);
f1();//no more argument, function argument stored in f1
//and this print "Foo 2" (:
//and normal function
boost::function<void ()> b1 = boost::bind(&Bar, 2);
b1();// print "Bar 2"

그리고 가장 유용합니다. boost :: bind 및 boost :: function을 사용하면 클래스 메서드에서 펑터를 만들 수 있습니다. 실제로 이것은 대리자입니다.

class SomeClass
{
    std::string state_;
public:
    SomeClass(const char* s) : state_(s) {}

    void method( std::string param )
    {
        std::cout << state_ << param << std::endl;
    }
};
SomeClass *inst = new SomeClass("Hi, i am ");
boost::function< void (std::string) > callback;
callback = boost::bind(&SomeClass::method, inst, _1);//create delegate
//_1 is a placeholder it holds plase for parameter
callback("useless");//prints "Hi, i am useless"

펑터 목록 또는 벡터를 만들 수 있습니다.

std::list< boost::function<void (EventArg e)> > events;
//add some events
....
//call them
std::for_each(
        events.begin(), events.end(), 
        boost::bind( boost::apply<void>(), _1, e));

이 모든 것에는 한 가지 문제가 있습니다. 컴파일러 오류 메시지는 사람이 읽을 수 없습니다. :)


Functor는 함수처럼 작동하는 객체입니다. 기본적으로 operator().

class MyFunctor
{
   public:
     int operator()(int x) { return x * 2;}
}

MyFunctor doubler;
int x = doubler(5);

진짜 장점은 펑터가 상태를 유지할 수 있다는 것입니다.

class Matcher
{
   int target;
   public:
     Matcher(int m) : target(m) {}
     bool operator()(int x) { return x == target;}
}

Matcher Is5(5);

if (Is5(n))    // same as if (n == 5)
{ ....}

"펑터"라는 이름은 C ++가 등장하기 훨씬 전에 카테고리 이론 에서 전통적으로 사용되었습니다 . 이것은 functor의 C ++ 개념과 관련이 없습니다. C ++에서 "functor"라고 부르는 것 대신 이름 함수 객체 를 사용하는 것이 좋습니다 . 이것이 다른 프로그래밍 언어가 유사한 구조를 호출하는 방법입니다.

일반 기능 대신 사용 :

풍모:

  • 함수 객체는 상태를 가질 수 있습니다.
  • 함수 객체는 OOP에 적합합니다 (다른 모든 객체처럼 동작 함).

단점 :

  • 프로그램이 더 복잡해집니다.

함수 포인터 대신 사용 :

풍모:

  • 함수 개체는 종종 인라인 될 수 있습니다.

단점 :

  • 함수 객체는 런타임 동안 다른 함수 객체 유형으로 교체 할 수 없습니다 (적어도 일부 기본 클래스를 확장하여 오버 헤드를 제공하지 않는 한).

가상 기능 대신 사용 :

풍모:

  • 함수 객체 (비가 상)에는 vtable 및 런타임 디스패치가 필요하지 않으므로 대부분의 경우 더 효율적입니다.

단점 :

  • 함수 객체는 런타임 동안 다른 함수 객체 유형으로 교체 할 수 없습니다 (적어도 일부 기본 클래스를 확장하여 오버 헤드를 제공하지 않는 한).

다른 사람들이 언급했듯이 functor는 함수처럼 작동하는 객체입니다. 즉, 함수 호출 연산자를 오버로드합니다.

Functor는 STL 알고리즘에서 일반적으로 사용됩니다. 함수 언어의 클로저와 같이 함수 호출 전후에 상태를 유지할 수 있기 때문에 유용합니다. 예를 들어, MultiplyBy인수에 지정된 양을 곱하는 펑터를 정의 할 수 있습니다 .

class MultiplyBy {
private:
    int factor;

public:
    MultiplyBy(int x) : factor(x) {
    }

    int operator () (int other) const {
        return factor * other;
    }
};

그런 다음 MultiplyBystd :: transform과 같은 알고리즘에 객체를 전달할 수 있습니다 .

int array[5] = {1, 2, 3, 4, 5};
std::transform(array, array + 5, array, MultiplyBy(3));
// Now, array is {3, 6, 9, 12, 15}

함수에 대한 포인터에 대한 functor의 또 다른 장점은 더 많은 경우 호출이 인라인 될 수 있다는 것입니다. 함수 포인터를에 전달한 transform경우 해당 호출이 인라인되고 컴파일러가 항상 동일한 함수를 전달한다는 것을 인식하지 않는 한 포인터를 통해 호출을 인라인 할 수 없습니다.


저와 같은 초보자를 위해 : 약간의 조사 끝에 jalf가 게시 한 코드가 무엇을했는지 알아 냈습니다.

펑 터는 함수처럼 "호출"될 수있는 클래스 또는 구조체 객체입니다. 이것은 () operator. () operator(확실하지 그이라고 부르는)는 인수의 수를 취할 수 있습니다. 다른 연산자는 두 개만 가져옵니다. 즉, + operator두 개의 값 (연산자의 각 측면에 하나씩) 만 가져 와서 오버로드 한 값을 반환 할 수 있습니다. () operator유연성을 제공하는 내부에 여러 인수를 넣을 수 있습니다 .

펑터를 만들려면 먼저 클래스를 만들어야합니다. 그런 다음 선택한 유형 및 이름의 매개 변수를 사용하여 클래스에 대한 생성자를 만듭니다. 이것은 동일한 명령문에서 생성자에 대해 이전에 선언 된 매개 변수를 사용하여 클래스 멤버 객체를 생성하는 이니셜 라이저 목록 (단일 콜론 연산자를 사용함)이 뒤 따릅니다. 그런 다음 () operator과부하입니다. 마지막으로 생성 한 클래스 또는 구조체의 전용 객체를 선언합니다.

내 코드 (jalf의 변수 이름이 혼란 스러움을 발견했습니다)

class myFunctor
{ 
    public:
        /* myFunctor is the constructor. parameterVar is the parameter passed to
           the constructor. : is the initializer list operator. myObject is the
           private member object of the myFunctor class. parameterVar is passed
           to the () operator which takes it and adds it to myObject in the
           overloaded () operator function. */
        myFunctor (int parameterVar) : myObject( parameterVar ) {}

        /* the "operator" word is a keyword which indicates this function is an 
           overloaded operator function. The () following this just tells the
           compiler that () is the operator being overloaded. Following that is
           the parameter for the overloaded operator. This parameter is actually
           the argument "parameterVar" passed by the constructor we just wrote.
           The last part of this statement is the overloaded operators body
           which adds the parameter passed to the member object. */
        int operator() (int myArgument) { return myObject + myArgument; }

    private: 
        int myObject; //Our private member object.
}; 

이 중 하나라도 부정확하거나 명백히 잘못된 경우 저를 바로 잡으십시오!


functor는 매개 변수화 된 (즉, 템플릿 화 된) 유형에 함수 를 적용하는 고차 함수 입니다. 지도 고차 함수 의 일반화입니다 . 예를 들어 다음 std::vector과 같이 펑터를 정의 할 수 있습니다 .

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::vector<U> fmap(F f, const std::vector<T>& vec)
{
    std::vector<U> result;
    std::transform(vec.begin(), vec.end(), std::back_inserter(result), f);
    return result;
}

이 함수는 a를 취하고 std::vector<T>를 반환 std::vector<U>하는 함수 F주어지면을 T반환합니다 U. functor는 컨테이너 유형에 대해 정의 할 필요가 없으며 다음을 포함하여 모든 템플릿 유형에 대해서도 정의 할 수 있습니다 std::shared_ptr.

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::shared_ptr<U> fmap(F f, const std::shared_ptr<T>& p)
{
    if (p == nullptr) return nullptr;
    else return std::shared_ptr<U>(new U(f(*p)));
}

다음은 유형을 다음으로 변환하는 간단한 예입니다 double.

double to_double(int x)
{
    return x;
}

std::shared_ptr<int> i(new int(3));
std::shared_ptr<double> d = fmap(to_double, i);

std::vector<int> is = { 1, 2, 3 };
std::vector<double> ds = fmap(to_double, is);

펑터가 따라야 할 두 가지 법칙이 있습니다. 먼저 펑이 식별 기능을 부여하는 경우, 그것이 그 상기 유형 식별 기능을 적용하는 것과 동일하게되어야한다고 식별 법이다 fmap(identity, x)동일해야 identity(x):

struct identity_f
{
    template<class T>
    T operator()(T x) const
    {
        return x;
    }
};
identity_f identity = {};

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<int> is1 = fmap(identity, is);
std::vector<int> is2 = identity(is);

다음 법칙은 합성 법칙으로, 펑터에 두 함수의 구성이 주어지면 첫 번째 함수에 펑터를 적용한 다음 두 번째 함수에 다시 적용하는 것과 동일해야합니다. 따라서 다음과 fmap(std::bind(f, std::bind(g, _1)), x)같아야합니다 fmap(f, fmap(g, x)).

double to_double(int x)
{
    return x;
}

struct foo
{
    double x;
};

foo to_foo(double x)
{
    foo r;
    r.x = x;
    return r;
}

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<foo> is1 = fmap(std::bind(to_foo, std::bind(to_double, _1)), is);
std::vector<foo> is2 = fmap(to_foo, fmap(to_double, is));

내 문제를 해결하기 위해 Functor를 사용해야하는 실제 상황은 다음과 같습니다.

나는 함수 세트 (예 : 20 개)를 가지고 있으며, 각각 3 개의 특정 지점에서 서로 다른 특정 함수를 호출하는 것을 제외하고는 모두 동일합니다.

이것은 엄청난 낭비이며 코드 중복입니다. 일반적으로 함수 포인터를 전달하고 3 개 지점에서 호출합니다. (따라서 코드는 20 번이 아니라 한 번만 나타나야합니다.)

하지만 각 경우에 특정 기능에는 완전히 다른 매개 변수 프로필이 필요하다는 것을 깨달았습니다! 때로는 2 개의 매개 변수, 때로는 5 개의 매개 변수 등이 있습니다.

또 다른 해결책은 특정 함수가 파생 클래스에서 재정의 된 메서드 인 기본 클래스를 갖는 것입니다. 하지만 함수 포인터를 전달할 수 있도록이 모든 INHERITANCE를 구축하고 싶습니까 ????

솔루션 : 그래서 제가 한 것은 제가 필요한 함수를 호출 할 수있는 래퍼 클래스 ( "Functor")를 만드는 것입니다. 매개 변수 등으로 미리 설정 한 다음 함수 포인터 대신 전달합니다. 이제 호출 된 코드는 내부에서 무슨 일이 일어나고 있는지 알지 못해도 Functor를 트리거 할 수 있습니다. 여러 번 호출 할 수도 있습니다 (3 번 호출해야했습니다).


그게 다입니다. Functor가 명확하고 쉬운 솔루션으로 판명 된 실용적인 예입니다.이를 통해 코드 중복을 20 개 함수에서 1 개로 줄일 수있었습니다.


콜백에서 사용되는 경우를 제외하고 C ++ 펑 터는 매트릭스 클래스에 대한 액세스 스타일을 선호 하는 Matlab 을 제공하는 데 도움이 될 수 있습니다 . .


펑 터는 gtkmm에서 일부 GUI 버튼을 실제 C ++ 함수 또는 메소드에 연결하는 데 사용됩니다.


pthread 라이브러리를 사용하여 앱을 다중 스레드로 만들면 Functor가 도움이 될 수 있습니다.
스레드를 시작하기 위해의 인수 중 하나는 pthread_create(..)자신의 스레드에서 실행할 함수 포인터입니다.
하지만 한 가지 불편한 점이 있습니다. 이 포인터는 정적 메서드 가 아니거나 같은 클래스지정 하지 않는 한 메서드에 대한 포인터가 될 수 없습니다 class::method. 그리고 또 한가지, 당신의 메소드의 인터페이스는 다음과 같을 수 있습니다 :

void* method(void* something)

따라서 추가 작업없이 스레드에서 클래스의 메서드를 실행할 수 없습니다 (간단한 방법으로).

C ++에서 스레드를 처리하는 아주 좋은 방법은 자신 만의 Thread클래스를 만드는 것 입니다. MyClass클래스에서 메서드를 실행하려면 해당 메서드를 Functor파생 클래스 로 변환했습니다 .

또한 Thread클래스에는 다음 메서드가 있습니다.이 메서드에 static void* startThread(void* arg)
대한 포인터는를 호출하는 인수로 사용됩니다 pthread_create(..). 그리고 startThread(..)arg에서 받아야 void*하는 것은 모든 Functor파생 클래스의 힙에있는 인스턴스에 대한 캐스팅 된 참조 이며, Functor*실행될 때 다시 캐스팅 된 다음 run()메서드 를 호출합니다 .


을 추가하기 위해 기존 레거시 메서드를 명령 패턴에 맞추기 위해 함수 개체를 사용했습니다. (오직 OO 패러다임의 아름다움이 진정한 OCP가 느꼈던 곳) 또한 여기에 관련 기능 어댑터 패턴을 추가합니다.

메서드에 서명이 있다고 가정합니다.

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

우리는 그것을 Command 패턴에 맞추는 방법을 볼 것입니다.이를 위해서는 먼저 함수 객체로 호출 될 수 있도록 멤버 함수 어댑터를 작성해야합니다.

참고-이것은 추악하고 Boost bind 도우미 등을 사용할 수 있지만 할 수 없거나 원하지 않는 경우 이것은 한 가지 방법입니다.

// 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
};

또한 위의 클래스를 호출하는 데 도움이되는 도우미 메서드 mem_fun3이 필요합니다.

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));

}

이제 매개 변수를 바인딩하기 위해 바인더 함수를 작성해야합니다. 그래서 여기에 간다 :

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;
};

그리고 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);
}

이제 이것을 Command 클래스와 함께 사용해야합니다. 다음 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;
}

이름은 다음과 같습니다.

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

참고 : f3 (); task1-> ThreeParameterTask (21,22,23); 메소드를 호출합니다.

다음 링크 에서이 패턴의 전체 컨텍스트


반복 된 것처럼, functor는 함수로 취급 할 수있는 클래스입니다 (오버로드 연산자 ()).

일부 데이터를 함수에 대한 반복 또는 지연 호출과 연관시켜야하는 상황에 가장 유용합니다.

예를 들어, 펑터의 링크 목록은 기본적인 낮은 오버 헤드 동기 코 루틴 시스템, 작업 디스패처 또는 인터럽트 가능한 파일 구문 분석을 구현하는 데 사용될 수 있습니다. 예 :

/* prints "this is a very simple and poorly used task queue" */
class Functor
{
public:
    std::string output;
    Functor(const std::string& out): output(out){}
    operator()() const
    {
        std::cout << output << " ";
    }
};

int main(int argc, char **argv)
{
    std::list<Functor> taskQueue;
    taskQueue.push_back(Functor("this"));
    taskQueue.push_back(Functor("is a"));
    taskQueue.push_back(Functor("very simple"));
    taskQueue.push_back(Functor("and poorly used"));
    taskQueue.push_back(Functor("task queue"));
    for(std::list<Functor>::iterator it = taskQueue.begin();
        it != taskQueue.end(); ++it)
    {
        *it();
    }
    return 0;
}

/* prints the value stored in "i", then asks you if you want to increment it */
int i;
bool should_increment;
int doSomeWork()
{
    std::cout << "i = " << i << std::endl;
    std::cout << "increment? (enter the number 1 to increment, 0 otherwise" << std::endl;
    std::cin >> should_increment;
    return 2;
}
void doSensitiveWork()
{
     ++i;
     should_increment = false;
}
class BaseCoroutine
{
public:
    BaseCoroutine(int stat): status(stat), waiting(false){}
    void operator()(){ status = perform(); }
    int getStatus() const { return status; }
protected:
    int status;
    bool waiting;
    virtual int perform() = 0;
    bool await_status(BaseCoroutine& other, int stat, int change)
    {
        if(!waiting)
        {
            waiting = true;
        }
        if(other.getStatus() == stat)
        {
            status = change;
            waiting = false;
        }
        return !waiting;
    }
}

class MyCoroutine1: public BaseCoroutine
{
public:
    MyCoroutine1(BaseCoroutine& other): BaseCoroutine(1), partner(other){}
protected:
    BaseCoroutine& partner;
    virtual int perform()
    {
        if(getStatus() == 1)
            return doSomeWork();
        if(getStatus() == 2)
        {
            if(await_status(partner, 1))
                return 1;
            else if(i == 100)
                return 0;
            else
                return 2;
        }
    }
};

class MyCoroutine2: public BaseCoroutine
{
public:
    MyCoroutine2(bool& work_signal): BaseCoroutine(1), ready(work_signal) {}
protected:
    bool& work_signal;
    virtual int perform()
    {
        if(i == 100)
            return 0;
        if(work_signal)
        {
            doSensitiveWork();
            return 2;
        }
        return 1;
    }
};

int main()
{
     std::list<BaseCoroutine* > coroutineList;
     MyCoroutine2 *incrementer = new MyCoroutine2(should_increment);
     MyCoroutine1 *printer = new MyCoroutine1(incrementer);

     while(coroutineList.size())
     {
         for(std::list<BaseCoroutine *>::iterator it = coroutineList.begin();
             it != coroutineList.end(); ++it)
         {
             *it();
             if(*it.getStatus() == 0)
             {
                 coroutineList.erase(it);
             }
         }
     }
     delete printer;
     delete incrementer;
     return 0;
}

물론 이러한 예는 그 자체로 유용하지 않습니다. 펑터가 얼마나 유용 할 수 있는지 보여줄뿐 펑터 자체는 매우 기본적이고 융통성이 없기 때문에 예를 들어 부스트가 제공하는 것보다 덜 유용합니다.


함수를 펑터로 구현할 때의 큰 장점은 호출간에 상태를 유지하고 재사용 할 수 있다는 것입니다. 예를 들어, 문자열 간의 Levenshtein 거리 를 계산 하기 위한 Wagner-Fischer 알고리즘 과 같은 많은 동적 프로그래밍 알고리즘 은 큰 결과 테이블을 채우는 방식으로 작동합니다. 함수가 호출 될 때마다이 테이블을 할당하는 것은 매우 비효율적이므로 함수를 펑터로 구현하고 테이블을 멤버 변수로 만들면 성능이 크게 향상 될 수 있습니다.

다음은 Wagner-Fischer 알고리즘을 펑터로 구현 한 예입니다. 테이블이 생성자에 할당 된 다음 operator()필요에 따라 크기를 조정하여 에서 재사용되는 방법 을 확인하십시오.

#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];
    }
};

Functor는 함수 내에서 로컬 함수 정의를 시뮬레이션하는 데 사용할 수도 있습니다. 질문다른 것을 참조하십시오 .

그러나 로컬 펑 터는 외부 자동 변수에 접근 할 수 없습니다. 람다 (C ++ 11) 함수가 더 나은 솔루션입니다.


나는 functor의 매우 흥미로운 사용을 "발견"했습니다 : functor는 이름이없는 방법이기 때문에 한 가지 방법에 대해 좋은 이름이 없을 때 사용합니다 ;-)

참고 URL : https://stackoverflow.com/questions/356950/what-are-c-functors-and-their-uses

반응형