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


875

나는 C ++의 functors에 대해 많은 이야기를 계속합니다. 누군가 나에게 그들이 무엇이고 어떤 경우에 유용 할 것인지에 대한 개요를 줄 수 있습니까?


4
이 주제는 this question에 대한 답변으로 다루어졌습니다. stackoverflow.com/questions/317450/why-override-operator#317528
Luc Touraille

2
C ++에서 클로저를 만드는 데 사용됩니다.
copper.hat

누군가가 무엇을 operator()(...)의미 하는지 궁금하다면 아래의 답변을 살펴보십시오 . "함수 호출" 연산자에 과부하가 걸리고 있습니다. 단순히 운영자에게 운영자 과부하입니다 (). operator()이라는 함수를 호출하는 데 실수하지 말고 operator일반적인 연산자 오버로드 구문으로보십시오.
zardosht

답변:


1041

functor는 거의 operator ()를 정의하는 클래스입니다. 그러면 함수처럼 보이는 객체를 만들 수 있습니다.

// this is a functor
struct add_x {
  add_x(int val) : x(val) {}  // Constructor
  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 또는 다른 표준 라이브러리 알고리즘과 같은 다른 함수에 대한 인수로 함수를 인수로 전달하는 경우가 많습니다. 위에서 언급했듯이 함수는 상태를 포함하고 있기 때문에 "커스터마이즈"될 수 있다는 점을 제외하고는 일반 함수 포인터로 동일한 작업을 수행 할 수 있습니다 (함수 포인터를 사용하려면 함수를 작성해야 함). 이 함수는 인수에 정확히 1을 더했습니다. functor는 일반적이며 초기화 한 모든 것을 추가합니다.) 또한 잠재적으로 더 효율적입니다. 위 예제에서 컴파일러는 어떤 함수 std::transform를 호출해야하는지 정확히 알고 있습니다. 전화해야합니다 add_x::operator(). 즉, 해당 함수 호출을 인라인 할 수 있습니다. 그러면 벡터의 각 값에 대해 수동으로 함수를 호출 한 것처럼 효율적입니다.

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


32
이 줄을 설명해 주시겠습니까? std :: transform (in.begin (), in.end (), out.begin (), add_x (1)); 왜 add42가 아닌 add_x를 작성합니까?
Alecs

102
@Alecs 둘 다 효과가 있었지만 효과는 달라졌을 것입니다. 를 사용했다면 add42이전에 만든 펑터를 사용하고 각 값에 42를 추가했을 것입니다. 로 add_x(1)i를 펑, 각 값에 1을 추가 한 새로운 인스턴스를 생성한다. 단순히 functor를 먼저 작성하고 실제로 어떤 용도로든 사용하기 전에 먼저 작성하지 않고 필요할 때 "즉석에서"즉석에서 인스턴스화한다는 것을 보여주기 위해서입니다.
jalf

8
물론 @zadane. operator()호출자가 호출하기 위해 사용하는 것이기 때문에을 가져야 합니다. 어떤 다른 펑이 멤버 함수, 생성자, 연산자 및 멤버 변수의가 당신에게 완전히이다.
jalf

4
@ rikimaru2013 함수형 프로그래밍의 관점에서 보면 함수는 functor이지만 C ++에서는 functor는 특히 함수로 사용되는 클래스입니다. 이 용어는 초기에 조금 남용되었지만,이 구분은 유용한 구분이어서 오늘날에도 지속됩니다. C ++ 컨텍스트에서 함수를 "펑터 (functors)"로 언급하기 시작하면 대화를 혼동하게됩니다.
srm

6
클래스입니까, 아니면 클래스의 인스턴스입니까? 대부분의 소스에서 add42functor라고 불리며 functor add_x의 클래스 또는 functor 클래스가 아닙니다 . functors는 함수 클래스 가 아니라 함수 객체 라고도하기 때문에 용어가 일관된 것으로 나타났습니다 . 이 점을 명확히 할 수 있습니까?
Sergei Tachenov 2016 년

121

작은 추가. 을 사용 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를 사용하여이 functor에 상태를 추가 할 수 있습니다.

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을 사용하면 클래스 메소드에서 functor를 만들 수 있습니다. 실제로 이것은 대리자입니다.

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

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


4
operator ()클래스가 기본적으로 private로 설정되어 있으므로 첫 번째 예제에서 공개 해서는 안 됩니까?
NathanOliver 2016 년

4
어쩌면 어느 시점 에서이 답변은 업데이트가 필요합니다. 이제 람다는 무엇이든 functor를 얻는 가장 쉬운 방법
이므로

102

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)
{ ....}

11
함수 포인터처럼 사용할 수 있다는 것을 추가하면됩니다.
Martin York

7
@LokiAstari-개념이 처음 인 사람들에게는 약간 오해의 소지가 있습니다. 펑 터는 "처럼"사용할 수 있지만 함수 포인터를 "대신"대신 사용할 수는 없습니다. 예를 들어, 함수 포인터를 사용하는 함수는 함수 함수가 함수 포인터와 동일한 인수 및 반환 값을 가지고 있어도 대체 함수를 대신 할 수 없습니다. 그러나 디자인 할 때 펑 터는 선호되고 이론적으로 "더 현대적인"방법입니다.
MasonWinsauer

두 번째가 왜 반환 int해야 bool합니까? 이것은 C가 아니라 C ++입니다.이 답변이 작성되었을 때 bool존재 하지 않았습니까?
Fund Monica의 소송

@QPaysTaxes 오타라고 생각합니다. 첫 번째 예제에서 코드를 복사하여 붙여 넣은 후 변경하지 않은 것 같습니다. 지금 고쳤습니다.
James Curran

1
@Riasat Matcher가 라이브러리에 있으면 Is5 ()를 정의하는 것은 매우 간단합니다. 그리고 당신은 Is7 (), Is32 () 등을 만들 수 있습니다. 또한, 그것은 단지 예일뿐입니다. functor는 훨씬 더 복잡 할 수 있습니다.
제임스 쿠란

51

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

일반 기능 대신 사용 :

풍모:

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

단점 :

  • 프로그램을 더 복잡하게 만듭니다.

함수 포인터 대신 사용 :

풍모:

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

단점 :

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

가상 기능 대신 사용 :

풍모:

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

단점 :

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

1
실제 사례에서 이러한 사용 사례를 설명 할 수 있습니까? 함수를 다형성 및 함수 포인터로 사용하는 방법은 무엇입니까?
Milad Khajavi

1
실제로 functor가 상태를 유지한다는 것은 무엇을 의미합니까?
erogol

일종의 다형성을 가지려면 기본 클래스가 필요하다는 점을 지적 해 주셔서 감사합니다. 나는 단순한 함수 포인터와 같은 장소에서 functor를 사용해야한다는 문제가 있으며 내가 찾은 유일한 방법은 functor 기본 클래스를 작성하는 것입니다 (C ++ 11을 사용할 수 없기 때문에). 내가 당신의 대답을 읽을 때 까지이 오버 헤드가 의미가 있는지 확실하지 않았습니다.
idclev 463035818

1
@Erogol functor는 구문을 지원하는 객체입니다 foo(arguments). 따라서 변수를 포함 할 수 있습니다. 예를 들어, update_password(string)기능 이있는 경우 얼마나 자주 발생했는지 추적 할 수 있습니다. functor를 사용 private long time하면 마지막으로 발생한 타임 스탬프를 나타낼 수 있습니다 . 함수 포인터 또는 일반 함수를 사용하면 네임 스페이스 외부의 변수를 사용해야합니다. 네임 스페이스 외부에서는 정의가 아닌 문서 및 사용과 직접 관련되어 있습니다.
l

4
⁺¹은 (는) 아무런 이유없이 이름이 만들어 졌다고 언급했습니다. 방금 수학 함수 (및 원하는 경우 기능적) functor와 C ++의 함수 사이의 관계를 검색했습니다 .
Hi-Angel

41

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

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

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되지 않고 컴파일러가 항상 동일한 함수를 전달한다는 것을 알지 않는 한 포인터를 통해 호출을 인라인 할 수 없습니다.


37

우리 중 저와 같은 초보자들을 위해 : 약간의 연구를 거친 후 jalf가 게시 한 코드가 무엇인지 알아 냈습니다.

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

functor를 만들려면 먼저 클래스를 만듭니다. 그런 다음 선택한 유형과 이름의 매개 변수를 사용하여 클래스에 생성자를 만듭니다. 이것은 동일한 명령문에서 이전에 선언 된 매개 변수를 사용하여 생성자에 클래스 멤버 객체를 구성하는 이니셜 라이저 목록 (단일 콜론 연산자를 사용합니다)을 사용합니다. 그런 다음 () 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.
}; 

이 중 하나라도 부정확하거나 명백한 틀린 점이 있으면 언제든지 바로 정정하십시오!


1
() 연산자를 함수 호출 연산자라고합니다. 괄호 연산자라고도 할 수 있습니다.
Gautam

4
"우리가 쓴 생성자에 의해 전달"parameterVar "이 매개 변수는 실제로 인수입니다" 허?
궤도에서 가벼움 레이스

22

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

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를 가져와 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)));
}

다음은 형식을 a로 변환하는 간단한 예입니다 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);

펑터가 따라야하는 두 가지 법률이 있습니다. 첫 번째는 신분법으로, functor에 신분 함수가 부여되면, 신분 함수를 유형에 적용 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);

다음 법칙은 구성 법칙으로, functor에 두 기능의 구성이 주어지면 functor를 첫 번째 기능에 적용 한 다음 다시 두 번째 기능에 적용하는 것과 동일해야한다고 명시합니다. 따라서 다음과 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));

2
functor가 이러한 의미로 올바르게 사용되어야하고 ( en.wikipedia.org/wiki/Functor 참조 ), 함수 객체에 사용하는 것은 엉성 하다고 주장하는 기사 : jackieokay.com/2017/01/26/functors.html It 함수 객체의 의미만을 고려하는 답변의 수가 주어지면 너무 늦을 수 있습니다.
armb

2
이 답변은> 700 Upvotes의 답변이어야합니다. 누군가 C ++보다 Haskell을 더 잘 아는 것처럼 C ++ 링구아는 항상 나를 혼란스럽게했습니다.
mschmidt

카테고리 이론과 C ++? 이 Bartosz Milewski의 비밀 SO 계정입니까?
Mateen Ulhaq

1
표준 표기법으로 펑터 법을 요약하는 것이 도움이 될 수 있습니다 fmap(id, x) = id(x)fmap(f ◦ g, x) = fmap(f, fmap(g, x)).
Mateen Ulhaq

@mschmidt functor도 이것을 의미하지만 C ++은 "함수 객체"와 같은 의미로 이름을 오버로드합니다.
Caleth

9

다음은 Functor를 사용하여 문제를 해결 해야하는 실제 상황입니다.

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

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

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

또 다른 해결책은 기본 클래스를 갖는 것입니다. 여기서 특정 함수는 파생 클래스에서 재정의 된 메서드입니다. 그러나이 상속을 모두 작성하고 싶습니까? 그래서 함수 포인터를 전달할 수 있습니까 ????

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


Functor가 명확하고 쉬운 솔루션으로 판명 된 실제 사례로, 코드 중복을 20 개의 함수에서 1로 줄일 수있었습니다.


3
functor가 다른 특정 함수를 호출하고 이러한 다른 함수가 수용하는 매개 변수의 수가 다른 경우 functor가 다른 함수로 디스패치하기 위해 가변 개수의 인수를 수락 했습니까?
johnbakers

4
코드의 일부를 인용하여 위의 시나리오를 설명해 주시겠습니까? 저는이 개념을 이해하고자하는 C ++에
익숙하지 않습니다

3

콜백에 사용되는 것을 제외하고 C ++ functors는 Matlab과 같은 액세스 스타일을 매트릭스 클래스 에 제공 할 수 있습니다 . 이 .


이것은 (매트릭스 예제) operator()함수 객체 속성을 사용하지만 사용 하지는 않습니다.
renardesque

3

반복되는 것처럼 functors는 함수로 처리 될 수있는 클래스입니다 (과부하 연산자 ()).

그것들은 함수에 대한 반복 또는 지연된 호출과 일부 데이터를 연결해야하는 상황에 가장 유용합니다.

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

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

물론 이러한 예제는 그 자체로는 유용하지 않습니다. 그들은 functors가 어떻게 유용 할 수 있는지 보여주고 functors 자체는 매우 기본적이고 융통성이 없어서 예를 들어 boost가 제공하는 것보다 덜 유용합니다.


2

gtkmm에서는 Functors를 사용하여 일부 GUI 버튼을 실제 C ++ 함수 또는 메소드에 연결합니다.


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

void* method(void* something)

따라서 별도의 작업을 수행하지 않고 클래스의 메소드를 (간단한 방식으로) 스레드에서 실행할 수 없습니다.

C ++에서 스레드를 처리하는 가장 좋은 방법은 자신의 Thread클래스를 만드는 것 입니다. MyClass클래스에서 메소드를 실행하려면 내가 한 일은 해당 메소드를 Functor파생 클래스 로 변환하십시오 .

또한 Thread클래스에는 다음 static void* startThread(void* arg)
과 같은 메소드가 있습니다.이 메소드에 대한 포인터는 call의 인수로 사용됩니다 pthread_create(..). 그리고 무엇 startThread(..)인수에 수신해야 할 것입니다 void*어떤 힙의 인스턴스에 주조 참조 Functor위로 주조됩니다 파생 클래스, Functor*실행될 때, 그리고 다음의 호출 run()방법은.


2

에 추가하기 위해 기존 레거시 방법을 명령 패턴에 맞추기 위해 함수 객체를 사용했습니다. (OO 패러다임의 진정한 OCP의 아름다움이 느껴지는 곳만); 또한 여기에 관련 기능 어댑터 패턴을 추가하십시오.

메소드에 서명이 있다고 가정하십시오.

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

커맨드 패턴에 어떻게 맞출 수 있는지 살펴 보겠습니다. 먼저, 먼저 함수 객체로 호출 할 수 있도록 멤버 함수 어댑터를 작성해야합니다.

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

// 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); 메소드를 호출합니다.

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


2

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

아래는 Wagner-Fischer 알고리즘을 functor로 구현 한 예입니다. 생성자에서 테이블을 할당 한 다음 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];
    }
};

1

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

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


-10

나는 functors의 매우 흥미로운 사용법을 "발견했다": functor는 이름이없는 방법이므로 하나의 방법에 대해 좋은 이름이 없을 때 사용한다 ;-)


펑터를 "이름없는 방법"으로 묘사하는 이유는 무엇입니까?
Anderson Green

5
이름이없는 함수를 람다라고합니다.
Paul Fultz II
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.