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


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.