C ++ 펑터와 그 용도는 무엇입니까? 나는 C ++의 functors에

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



답변

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(). 즉, 해당 함수 호출을 인라인 할 수 있습니다. 그러면 벡터의 각 값에 대해 수동으로 함수를 호출 한 것처럼 효율적입니다.

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


답변

작은 추가. 을 사용 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));

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


답변

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


답변

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

일반 기능 대신 사용 :

풍모:

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

단점 :

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

함수 포인터 대신 사용 :

풍모:

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

단점 :

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

가상 기능 대신 사용 :

풍모:

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

단점 :

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

답변

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


답변

우리 중 저와 같은 초보자들을 위해 : 약간의 연구를 거친 후 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.
}; 

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


답변

펑 터는 매개 변수화 된 (즉, 템플릿 화 된) 유형에 함수 를 적용하는 고차 함수 입니다. 고차 함수 의 일반화입니다 . 예를 들어 다음 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));