C ++ 대리자는 무엇입니까?


147

C ++에서 델리게이트의 일반적인 아이디어는 무엇입니까? 그들은 무엇이며 어떻게 사용되며 무엇을 위해 사용됩니까?

먼저 '블랙 박스'방식으로 그들에 대해 배우고 싶지만, 이런 것들의 내장에 대한 약간의 정보도 훌륭 할 것입니다.

이것은 C ++이 가장 순수하거나 깨끗하지는 않지만 작업하는 코드베이스에 풍부함이 있음을 알았습니다. 나는 그것들을 충분히 이해하기를 바라고 있으므로 그냥 사용할 수 있고 끔찍한 중첩 템플릿 끔찍함을 탐구 할 필요가 없습니다.

이 두 가지 코드 프로젝트 기사는 내가 의미하는 바를 간결하게 설명하지는 않습니다.


4
.NET에서 관리되는 C ++에 대해 이야기하고 있습니까?
dasblinkenlight

2
위임 에 대한 Wikipedia 페이지를 보셨습니까 ?
Matthijs Bierman

5
delegateC ++ 용어에서 일반적인 이름이 아닙니다. 읽은 내용을 포함시키기 위해 질문에 정보를 추가해야합니다. 패턴이 일반적 일 수는 있지만 일반적으로 대리자 , C ++ CLI 또는 특정 delegate 구현을 가진 다른 라이브러리와 관련하여 이야기하면 대답이 다를 수 있습니다 .
David Rodríguez-dribeas

6
이년 기다려야)?
rank1

6
아직도 기다리고 ...
그림 Opiner

답변:


173

C ++에서 델리게이트를 달성하기위한 수많은 선택이 있습니다. 여기 내 마음에 온 것들이 있습니다.


옵션 1 : 펑터 :

함수 객체는 다음을 구현하여 만들 수 있습니다. operator()

struct Functor
{
     // Normal class/struct members

     int operator()(double d) // Arbitrary return types and parameter list
     {
          return (int) d + 1;
     }
};

// Use:
Functor f;
int i = f(3.14);

옵션 2 : 람다 식 ( C ++ 11 만 해당)

// Syntax is roughly: [capture](parameter list) -> return type {block}
// Some shortcuts exist
auto func = [](int i) -> double { return 2*i/1.15; };
double d = func(1);

옵션 3 : 함수 포인터

int f(double d) { ... }
typedef int (*MyFuncT) (double d);
MyFuncT fp = &f;
int a = fp(3.14);

옵션 4 : 멤버 함수에 대한 포인터 (가장 빠른 솔루션)

코드 프로젝트 에서 Fast C ++ Delegate를 참조하십시오 .

struct DelegateList
{
     int f1(double d) { }
     int f2(double d) { }
};

typedef int (DelegateList::* DelegateType)(double d);

DelegateType d = &DelegateList::f1;
DelegateList list;
int a = (list.*d)(3.14);

옵션 5 : 표준 :: 기능

(또는 boost::function표준 라이브러리가 지원하지 않는 경우). 느리지 만 가장 유연합니다.

#include <functional>
std::function<int(double)> f = [can be set to about anything in this answer]
// Usually more useful as a parameter to another functions

옵션 6 : 바인딩 ( std :: bind 사용 )

예를 들어 멤버 함수를 호출하는 데 편리한 일부 매개 변수를 미리 설정할 수 있습니다.

struct MyClass
{
    int DoStuff(double d); // actually a DoStuff(MyClass* this, double d)
};

std::function<int(double d)> f = std::bind(&MyClass::DoStuff, this, std::placeholders::_1);
// auto f = std::bind(...); in C++11

옵션 7 : 템플릿

인수 목록과 일치하는 한 아무 것도 허용하십시오.

template <class FunctionT>
int DoSomething(FunctionT func)
{
    return func(3.14);
}

2
훌륭한 목록, +1 그러나 람다와에서 반환 된 객체를 캡처하는 데 실제로 두 명만 대표자로 간주되며 std::bind람다는 다른 인수 유형을 허용 할 수 있다는 의미에서 다형성이 아니라는 점을 제외하고는 실제로 동일한 작업을 수행합니다.
Xeo

1
@ JN : 함수 포인터를 사용하지 않는 것이 좋지만 멤버 메소드 포인터를 사용하면 괜찮을 것 같습니다. 그들은 동일합니다!
Matthieu M.

1
@MatthieuM. : 페어 포인트. 함수 포인터를 레거시라고 생각하고 있었지만 아마도 내 개인적인 취향 일 것입니다.
JN

1
@ Xeo : 대리인에 대한 나의 생각은 다소 경험적입니다. 어쩌면 나는 너무 많은 함수 객체와 대리자를 혼합 할 수 있습니다 (이전 C # 경험을 비난하십시오).
JN

2
@SirYakalot 함수처럼 동작하지만 동시에 상태를 유지할 수 있고 다른 변수처럼 조작 할 수있는 것. 예를 들어 하나의 용도는 두 개의 매개 변수를 사용하는 함수를 사용하고 첫 번째 매개 변수가 특정 값을 갖도록 ( binding내 목록의) 단일 매개 변수를 사용하여 새 함수를 만드는 것 입니다. 함수 포인터로는이를 달성 할 수 없습니다.
JN

37

델리게이트는 객체 인스턴스에 대한 포인터 또는 참조를 래핑하는 클래스, 해당 객체 인스턴스에서 호출 할 객체 클래스의 멤버 메소드 및 해당 호출을 트리거하는 메소드를 제공합니다.

예를 들면 다음과 같습니다.

template <class T>
class CCallback
{
public:
    typedef void (T::*fn)( int anArg );

    CCallback(T& trg, fn op)
        : m_rTarget(trg)
        , m_Operation(op)
    {
    }

    void Execute( int in )
    {
        (m_rTarget.*m_Operation)( in );
    }

private:

    CCallback();
    CCallback( const CCallback& );

    T& m_rTarget;
    fn m_Operation;

};

class A
{
public:
    virtual void Fn( int i )
    {
    }
};


int main( int /*argc*/, char * /*argv*/ )
{
    A a;
    CCallback<A> cbk( a, &A::Fn );
    cbk.Execute( 3 );
}

통화를 시작하는 방법을 제공하는 것은 무엇입니까? 대리인? 어떻게? 함수 포인터?
SirYakalot

델리게이트 클래스는 Execute()델리게이트가 래핑하는 객체에서 함수 호출을 트리거하는 것과 같은 메소드를 제공합니다 .
Opiner Grimm

4
Execute 대신 호출 연산자 인 void CCallback :: operator () (int)를 재정의하는 것이 좋습니다. 그 이유는 일반적인 프로그래밍에서, 호출 가능한 객체는 함수처럼 호출 될 것으로 예상됩니다. o.Execute (5)는 호환되지 않지만 o (5)는 호출 가능한 템플릿 인수로 적합합니다. 이 수업은 일반화 될 수도 있지만 간결하게하기 위해 간단하게 유지한다고 가정합니다 (좋은 것입니다). 이 nitpick 이외에, 이것은 매우 유용한 클래스입니다.
David Peterson

18

C ++ 델리게이트 구현의 필요성은 C ++ 커뮤니티에 오랫동안 지속되어 온 것입니다. 모든 C ++ 프로그래머는 다음과 같은 사실에도 불구하고 사용하기를 원합니다.

  1. std::function() 힙 작업을 사용합니다 (심각한 임베디드 프로그래밍에 도달 할 수 없음).

  2. 다른 모든 구현은 이식성 또는 표준 적합성에 대한 양보를 크거나 작게 만듭니다 (여기 및 코드 프로젝트에서 다양한 델리게이트 구현을 검사하여 확인하십시오). 나는 와일드 reinterpret_casts를 사용하지 않는 구현, 사용자가 전달 한 것과 동일한 크기의 함수 포인터를 생성하는 중첩 클래스 "프로토 타입", 먼저 앞으로 선언과 같은 컴파일러 트릭, typedef를 다시 선언하는 구현을 보지 못했습니다. 이번에는 다른 클래스 또는 유사한 그늘진 기술에서 상속되었습니다. 그것을 구현 한 구현 자에게는 큰 성과이지만 C ++의 진화 방식에 대한 슬픈 증거입니다.

  3. C3 표준 개정이 3 개 이상인 델리게이트가 제대로 처리되지 않았다는 점은 거의 지적되지 않았습니다. (또는 간단한 델리게이트 구현을 허용하는 언어 기능이 부족합니다.)

  4. C ++ 11 람다 함수가 표준에 의해 정의되는 방식 (각 람다는 익명의 다른 유형을 가짐)으로 상황이 일부 유스 케이스에서만 개선되었습니다. 그러나 (DLL) 라이브러리 API에서 델리게이트를 사용하는 유스 케이스의 경우 람다 만으로는 여전히 사용할 수 없습니다. 여기에서 일반적인 기술은 먼저 람다를 std :: function으로 압축 한 다음 API를 통해 전달하는 것입니다.


다른 곳에서 본 다른 아이디어를 기반으로 C ++ 11에서 Elbert Mai의 일반 콜백 버전을 수행했으며 2)에서 언급 한 대부분의 문제를 해결하는 것으로 보입니다. 나에게 남아있는 유일한 잔소리 문제는 클라이언트 코드에서 매크로를 사용하여 대리자를 만드는 것입니다. 내가 아직 해결하지 않은 템플릿 마술 방법이있을 것입니다.
kert

3
임베디드 시스템에서 힙없이 std :: function을 사용하고 있으며 여전히 작동합니다.
prasad

1
std::function항상 동적으로 할당되는 것은 아닙니다
궤도의 가벼움 레이스

3
이 답변은 실제 답변보다 더 격렬한 것입니다
궤도의 가벼움 경주

8

간단히 말하면 델리게이트는 함수 포인터가 작동하는 방식에 대한 기능을 제공합니다. C ++에는 함수 포인터에 대한 많은 제한이 있습니다. 델리게이트는 배후의 템플릿 불쾌감을 사용하여 원하는 방식으로 작동하는 템플릿 클래스 함수 포인터 유형을 만듭니다.

즉-주어진 기능을 가리 키도록 설정할 수 있으며 언제 어디서나 전달할 수 있습니다.

여기에 좋은 예가 있습니다.


4

여기에 언급되지 않은 C ++의 대리자 옵션은 함수 ptr 및 컨텍스트 인수를 사용하여 C 스타일을 수행하는 것입니다. 이것은 아마도이 질문을 많은 사람들이 피하려고하는 것과 같은 패턴 일 것입니다. 그러나 패턴은 이식 가능하고 효율적이며 임베디드 및 커널 코드에서 사용할 수 있습니다.

class SomeClass
{
    in someMember;
    int SomeFunc( int);

    static void EventFunc( void* this__, int a, int b, int c)
    {
        SomeClass* this_ = static_cast< SomeClass*>( this__);

        this_->SomeFunc( a );
        this_->someMember = b + c;
    }
};

void ScheduleEvent( void (*delegateFunc)( void*, int, int, int), void* delegateContext);

    ...
    SomeClass* someObject = new SomeObject();
    ...
    ScheduleEvent( SomeClass::EventFunc, someObject);
    ...

1

표준 C ++의 함수 객체에 해당하는 Windows 런타임 전체 함수를 매개 변수로 사용할 수 있습니다 (실제로는 함수 포인터입니다). 주로 이벤트와 함께 사용됩니다. 대리인은 이벤트 처리기가 많이 이행하는 계약을 나타냅니다. 함수 포인터가 작동하는 방법을 용이하게합니다.

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