자유 함수가 예상되는 멤버 함수를 어떻게 전달할 수 있습니까?


122

질문은 다음과 같습니다.이 코드를 고려하십시오.

#include <iostream>


class aClass
{
public:
    void aTest(int a, int b)
    {
        printf("%d + %d = %d", a, b, a + b);
    }
};

void function1(void (*function)(int, int))
{
    function(1, 1);
}

void test(int a,int b)
{
    printf("%d - %d = %d", a , b , a - b);
}

int main (int argc, const char* argv[])
{
    aClass a();

    function1(&test);
    function1(&aClass::aTest); // <-- How should I point to a's aClass::test function?

    return 0;
}

어떻게 사용할 수있는 a'들 aClass::test에 대한 인수로 function1? 나는이 일에 갇혀있다.

클래스 멤버에 액세스하고 싶습니다.


1
이 답변을 살펴보십시오. stackoverflow.com/questions/2402579/… 그리고이 C ++ FAQ parashift.com/c++-faq/pointers-to-members.html
amdn

16
이것은 절대적으로 중복되지 않습니다 (적어도 연결된 특정 질문은 아닙니다). 그 질문은 함수에 대한 포인터 인 멤버를 선언하는 방법에 관한 것입니다. 이것은 비 정적 멤버 함수에 대한 포인터를 매개 변수로 전달하는 방법에 관한 것입니다.
CarLuva

답변:


144

함수 포인터를 사용하는 데 아무런 문제가 없습니다. 그러나 비 정적 멤버 함수에 대한 포인터는 일반 함수 포인터와 다릅니다. 멤버 함수는 함수에 대한 암시 적 인수로 전달되는 객체에서 호출되어야합니다. 위의 멤버 함수의 서명은 다음과 같습니다.

void (aClass::*)(int, int)

사용하려는 유형보다는

void (*)(int, int)

한 가지 접근 방식은 멤버 함수를 만드는 것으로 구성 될 수 있습니다. static이 경우 객체를 호출 할 필요가 없으며 유형과 함께 사용할 수 있습니다 void (*)(int, int).

당신이 당신의 클래스의 비 정적 멤버에 액세스해야하는 경우 당신이 함수는 C 인터페이스의 일부이기 때문에, 예를 들어, 함수 포인터, 고수해야 할, 당신의 최선의 선택은 항상를 전달하는 void*함수 포인터와 전화를 복용 함수에 에서 객체를 얻은 void*다음 멤버 함수를 호출 하는 전달 함수를 통해 멤버.

적절한 C ++ 인터페이스에서는 함수가 임의의 클래스 유형을 사용하기 위해 함수 개체에 대해 템플릿 인수를 사용하도록하는 방법을 살펴볼 수 있습니다. 템플릿 인터페이스를 사용하는 것이 바람직하지 않은 경우 다음과 같이 사용해야합니다 . std::function<void(int, int)>예를 들어를 사용하여 적절하게 호출 할 수있는 함수 객체를 만들 수 있습니다 std::bind().

클래스 유형 또는 적합한 것에 대한 템플릿 인수를 사용하는 유형 안전 접근 방식 은 잘못된 유형으로의 캐스트로 인한 오류 가능성을 제거하므로 인터페이스를 std::function<...>사용하는 것보다 바람직 void*합니다.

함수 포인터를 사용하여 멤버 함수를 호출하는 방법을 설명하기 위해 다음 예가 있습니다.

// the function using the function pointers:
void somefunction(void (*fptr)(void*, int, int), void* context) {
    fptr(context, 17, 42);
}

void non_member(void*, int i0, int i1) {
    std::cout << "I don't need any context! i0=" << i0 << " i1=" << i1 << "\n";
}

struct foo {
    void member(int i0, int i1) {
        std::cout << "member function: this=" << this << " i0=" << i0 << " i1=" << i1 << "\n";
    }
};

void forwarder(void* context, int i0, int i1) {
    static_cast<foo*>(context)->member(i0, i1);
}

int main() {
    somefunction(&non_member, 0);
    foo object;
    somefunction(&forwarder, &object);
}

좋아요,이 답변이 마음에 듭니다! "void *에서 객체를 가져온 다음 멤버 함수를 호출하는 전달 함수를 통해 멤버를 호출"한다는 의미를 지정하거나 유용한 링크를 공유 할 수 있습니까? 감사합니다
Jorge

나는 그것을 얻었다 고 생각한다. (귀하의 게시물을 편집했습니다) 설명과 예를 들어 주셔서 감사합니다. 정말 도움이되었습니다. 확인하기 위해, 내가 가리키고 싶은 모든 멤버 기능에 대해 전달자를 만들어야합니다. 권리?
Jorge

글쎄, 네. 템플릿을 얼마나 효율적으로 사용하고 있는지에 따라 다른 클래스 및 멤버 함수와 함께 작동 할 수있는 전달 템플릿을 만들 수 있습니다. 별도의 기능을 것이 작업을 수행하는 방법을, 나는 ;-) 생각하는 것
디트 마르 KUHL

1
이 답변은을 사용하기 때문에 싫어합니다. void*즉, 더 이상 입력하지 않기 때문에 매우 불쾌한 버그를 얻을 수 있습니다.
Superlokkus 2015 년

@Superlokkus : 대안으로 우리를 계몽 해 주시겠습니까?
Dietmar Kühl 2015-09-22

88

@Pete Becker의 대답은 괜찮지 만 C ++ 11에서 class명시 적 매개 변수로 인스턴스를 전달하지 않고도 수행 할 수 있습니다 function1.

#include <functional>
using namespace std::placeholders;

void function1(std::function<void(int, int)> fun)
{
    fun(1, 1);
}

int main (int argc, const char * argv[])
{
   ...

   aClass a;
   auto fp = std::bind(&aClass::test, a, _1, _2);
   function1(fp);

   return 0;
}

1
void function1(std::function<void(int, int)>)맞습니까?
Deqing 2015

2
함수 인수에 변수 이름을 지정하면 변수 이름이 실제로 전달됩니다. 그래서 : void function1(std::function<void(int, int)> functionToCall)그리고 functionToCall(1,1);. 나는 대답을 편집하려고했지만 누군가가 어떤 이유로 말이 안된다고 거부했습니다. 언젠가 upvoted 여부를 볼 수 있습니다.
Dorky Engineer 2015

1
@DorkyEngineer 꽤 이상 하군요. 당신이 옳다고 생각 합니다만, 그 오류가 어떻게 그렇게 오랫동안 눈에 띄지 않았을 지 모르겠습니다. 어쨌든 나는 지금 대답을 편집했습니다.
Matt Phillips

2
게시물 은 std :: function에서 심각한 성능 저하가 있다는 것을 발견했습니다 .
kevin

2
@kevin, 해당 게시물의 답변이 벤치 마크에서 결함을 보여 주었으므로 댓글을 수정하고 싶을 수 있습니다.
Jorge Leitao

54

멤버 함수에 대한 포인터는 함수에 대한 포인터와 다릅니다. 포인터를 통해 멤버 함수를 사용하려면 포인터 (분명히)와이를 적용 할 객체가 필요합니다. 해당 버전은 그래서 function1

void function1(void (aClass::*function)(int, int), aClass& a) {
    (a.*function)(1, 1);
}

그리고 그것을 부르려면 :

aClass a; // note: no parentheses; with parentheses it's a function declaration
function1(&aClass::test, a);

1
대단히 감사합니다. 방금 대괄호가 여기에서 계산된다는 것을 알았습니다 .function1 (& (aClass :: test), a) MSVC2013에서는 작동하지만 gcc에서는 작동하지 않습니다. (& 연산자가없는 클래스의 함수의 주소를 필요하기 때문에, 나는 혼란 찾을) GCC는 & 직접 클래스 이름 앞에 필요
kritzel_sw

functionin void (aClass::*function)(int, int)은 유형 이라고 생각 했습니다 . 왜냐하면 typedef void (aClass::*function)(int, int).
Olumide

@Olumide — typedef int X;유형을 정의합니다. int X;개체를 만듭니다.
Pete Becker

11

2011 년부터를 변경할 수 있다면 다음 function1과 같이 변경 하십시오.

#include <functional>
#include <cstdio>

using namespace std;

class aClass
{
public:
    void aTest(int a, int b)
    {
        printf("%d + %d = %d", a, b, a + b);
    }
};

template <typename Callable>
void function1(Callable f)
{
    f(1, 1);
}

void test(int a,int b)
{
    printf("%d - %d = %d", a , b , a - b);
}

int main()
{
    aClass obj;

    // Free function
    function1(&test);

    // Bound member function
    using namespace std::placeholders;
    function1(std::bind(&aClass::aTest, obj, _1, _2));

    // Lambda
    function1([&](int a, int b) {
        obj.aTest(a, b);
    });
}

( 라이브 데모 )

깨진 객체 정의를 수정 aClass a();했습니다 (함수를 선언합니다).


2

비슷한 질문을 했지만 ( C ++ openframeworks는 다른 클래스에서 void 전달 ) 찾은 대답이 더 명확했기 때문에 여기에 향후 기록에 대한 설명이 있습니다.

다음과 같이 std :: function을 사용하는 것이 더 쉽습니다.

 void draw(int grid, std::function<void()> element)

다음으로 전화하십시오.

 grid.draw(12, std::bind(&BarrettaClass::draw, a, std::placeholders::_1));

또는 더 쉽습니다.

  grid.draw(12, [&]{a.draw()});

참조로 캡처하는 객체를 호출하는 람다를 만드는 곳


1
이것이 CPP 태그라는 점을 감안할 때 이것이 가장 깨끗한 솔루션이라고 생각합니다. 모든 호출에서 복사하는 대신 std::functionin에 대한 상수 참조를 사용할 수 있습니다 draw.
Sohaib

2
이 예제에서는 함수가 매개 변수를 사용하지 않으므로 자리 표시자를 사용해서는 안됩니다.
kroiz

1

멤버 함수를 정적이고 모든 작업으로 만들었습니다.

#include <iostream>

class aClass
{
public:
    static void aTest(int a, int b)
    {
        printf("%d + %d = %d\n", a, b, a + b);
    }
};

void function1(int a,int b,void function(int, int))
{
    function(a, b);
}

void test(int a,int b)
{
    printf("%d - %d = %d\n", a , b , a - b);
}

int main (int argc, const char* argv[])
{
    aClass a;

    function1(10,12,test);
    function1(10,12,a.aTest); // <-- How should I point to a's aClass::test function?

    getchar();return 0;
}

"함수 1에 대한 인수로 a의 aClass :: test를 어떻게 사용할 수 있습니까?"라는 주요 질문을 해결할뿐만 아니라 뿐만 아니라 클래스 외부 코드 사용하여 수정 클래스 private 변수를 방지하는 것이 좋습니다
mathengineer

1

이 믿을 수 없을 정도로 간단한 솔루션이 왜 전달되었는지 확실하지 않습니다.

#include <stdio.h>

class aClass
{
public:
    void aTest(int a, int b)
    {
        printf("%d + %d = %d\n", a, b, a + b);
    }
};

template<class C>
void function1(void (C::*function)(int, int), C& c)
{
    (c.*function)(1, 1);
}
void function1(void (*function)(int, int)) {
  function(1, 1);
}

void test(int a,int b)
{
    printf("%d - %d = %d\n", a , b , a - b);
}

int main (int argc, const char* argv[])
{
    aClass a;

    function1(&test);
    function1<aClass>(&aClass::aTest, a);
    return 0;
}

산출:

1 - 1 = 0
1 + 1 = 2

0

실제로 인스턴스를 사용할 필요가없는 경우 a (예 : @mathengineer의 답변 처럼 정적으로 만들 수 있음 ) 비 캡처 람다를 간단히 전달할 수 있습니다. (함수 포인터로 붕괴 됨)


#include <iostream>

class aClass
{
public:
   void aTest(int a, int b)
   {
      printf("%d + %d = %d", a, b, a + b);
   }
};

void function1(void (*function)(int, int))
{
    function(1, 1);
}

int main()
{
   //note: you don't need the `+`
   function1(+[](int a,int b){return aClass{}.aTest(a,b);}); 
}

완드 박스


참고 : aClass건설 비용이 많이 들거나 부작용이있는 경우 이는 좋은 방법이 아닐 수 있습니다.


-1

이제 머리를 치는 것을 그만 둘 수 있습니다. 다음은 일반 C 함수를 인수 로받는 기존 함수 를 지원 하는 멤버 함수의 래퍼입니다 . 지침이 여기서 핵심입니다.thread_local

http://cpp.sh/9jhk3

// Example program
#include <iostream>
#include <string>

using namespace std;

typedef int FooCooker_ (int);

// Existing function
extern "C" void cook_10_foo (FooCooker_ FooCooker) {
    cout << "Cooking 10 Foo ..." << endl;
    cout << "FooCooker:" << endl;
    FooCooker (10);
}

struct Bar_ {
    Bar_ (int Foo = 0) : Foo (Foo) {};
    int cook (int Foo) {
        cout << "This Bar got " << this->Foo << endl;
        if (this->Foo >= Foo) {
            this->Foo -= Foo;
            cout << Foo << " cooked" << endl;
            return Foo;
        } else {
            cout << "Can't cook " <<  Foo << endl;
            return 0;
        }
    }
    int Foo = 0;
};

// Each Bar_ object and a member function need to define
// their own wrapper with a global thread_local object ptr
// to be called as a plain C function.
thread_local static Bar_* BarPtr = NULL;
static int cook_in_Bar (int Foo) {
    return BarPtr->cook (Foo);
}

thread_local static Bar_* Bar2Ptr = NULL;
static int cook_in_Bar2 (int Foo) {
    return Bar2Ptr->cook (Foo);
}

int main () {
  BarPtr = new Bar_ (20);
  cook_10_foo (cook_in_Bar);

  Bar2Ptr = new Bar_ (40);
  cook_10_foo (cook_in_Bar2);

  delete BarPtr;
  delete Bar2Ptr;
  return 0;
}

이 접근 방식과 관련된 문제에 대해 의견을 보내주십시오.

다른 답변은 기존 일반 C함수 를 호출하지 못합니다 : http://cpp.sh/8exun


3
따라서 std :: bind 또는 람다를 사용하여 인스턴스를 래핑하는 대신 전역 변수에 의존합니다. 다른 답변에 비해이 접근 방식의 이점을 볼 수 없습니다.
super

@super, 다른 답변은 일반 C함수를 인수로 사용 하는 기존 함수를 호출 할 수 없습니다 .
Necktwi

2
문제는 멤버 함수를 호출하는 방법입니다. 무료 기능을 전달하는 것은 이미 질문에서 OP를 위해 작동합니다. 당신은 또한 아무것도 전달하지 않습니다. 여기에는 하드 코딩 된 함수와 전역 Foo_ 포인터 만 있습니다. 다른 멤버 함수를 호출하려면 어떻게 확장할까요? 기본 함수를 다시 작성하거나 각 대상에 대해 다른 함수를 사용해야합니다.
super
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.