문제

함수 포인터를 콜백으로 전달해야 하는 API를 사용하고 있습니다.내 수업에서 이 API를 사용하려고 하는데 컴파일 오류가 발생합니다.

생성자에서 수행한 작업은 다음과 같습니다.

m_cRedundencyManager->Init(this->RedundencyManagerCallBack);

컴파일되지 않습니다. 다음 오류가 발생합니다.

오류 8 오류 C3867:'CLoggersInfra::RedundencyManagerCallBack':함수 호출 누락 인수 목록;멤버에 대한 포인터를 생성하려면 '&CLoggersInfra::RedundencyManagerCallBack'을 사용하세요.

나는 제안을 사용해 보았습니다. &CLoggersInfra::RedundencyManagerCallBack - 나한테는 안 맞았어.

이에 대한 제안/설명이 있나요?

VS2008을 사용하고 있습니다.

감사해요!!

도움이 되었습니까?

해결책

멤버 함수 포인터는 "this" 객체 인수를 기대하기 때문에 일반 함수 포인터처럼 처리될 수 없기 때문에 작동하지 않습니다.

대신 다음과 같이 정적 멤버 함수를 전달할 수 있습니다. 이는 이 점에서 일반 비멤버 함수와 같습니다.

m_cRedundencyManager->Init(&CLoggersInfra::Callback, this);

함수는 다음과 같이 정의될 수 있습니다.

static void Callback(int other_arg, void * this_pointer) {
    CLoggersInfra * self = static_cast<CLoggersInfra*>(this_pointer);
    self->RedundencyManagerCallBack(other_arg);
}

다른 팁

이것은 간단한 질문이지만 대답은 놀라울 정도로 복잡합니다.짧은 대답은 std::bind1st 또는 Boost::bind를 사용하여 수행하려는 작업을 수행할 수 있다는 것입니다.더 긴 답변은 아래에 있습니다.

컴파일러는 &CLoggersInfra::RedundencyManagerCallBack 사용을 제안하는 것이 정확합니다.첫째, RedundencyManagerCallBack이 멤버 함수인 경우 함수 자체는 CLoggersInfra 클래스의 특정 인스턴스에 속하지 않습니다.그것은 클래스 자체에 속합니다.이전에 정적 클래스 함수를 호출한 적이 있다면 동일한 SomeClass::SomeMemberFunction 구문을 사용하고 있음을 눈치챘을 것입니다.함수 자체는 특정 인스턴스가 아닌 클래스에 속한다는 의미에서 '정적'이므로 동일한 구문을 사용합니다.기술적으로 함수를 직접 전달하지 않기 때문에 '&'가 필요합니다. 함수는 C++에서 실제 개체가 아닙니다.대신 기술적으로 함수의 메모리 주소, 즉 함수의 명령이 메모리에서 시작되는 위치에 대한 포인터를 전달합니다.결과는 동일하지만 사실상 매개변수로 '함수를 전달'하는 것입니다.

하지만 이는 이 경우 문제의 절반에 불과합니다.내가 말했듯이 RedundencyManagerCallBack 함수는 특정 인스턴스에 '속하지' 않습니다.하지만 특정 인스턴스를 염두에 두고 콜백으로 전달하려는 것 같습니다.이를 수행하는 방법을 이해하려면 실제로 멤버 함수가 무엇인지 이해해야 합니다.추가 숨겨진 매개변수가 있는 일반 클래스에 정의되지 않은 함수입니다.

예를 들어:

class A {
public:
    A() : data(0) {}
    void foo(int addToData) { this->data += addToData; }

    int data;
};

...

A an_a_object;
an_a_object.foo(5);
A::foo(&an_a_object, 5); // This is the same as the line above!
std::cout << an_a_object.data; // Prints 10!

A::foo는 몇 개의 매개변수를 사용합니까?일반적으로 우리는 1이라고 말합니다.하지만 내부적으로는 foo가 실제로 2를 사용합니다.A::foo의 정의를 보면 'this' 포인터가 의미를 갖기 위해서는 A의 특정 인스턴스가 필요합니다(컴파일러는 'this'가 무엇인지 알아야 합니다).일반적으로 '이것'이 무엇인지 지정하는 방법은 MyObject.MyMemberFunction() 구문을 사용하는 것입니다.그러나 이는 MyObject의 주소를 MyMemberFunction의 첫 번째 매개변수로 전달하기 위한 구문 설탕일 뿐입니다.마찬가지로 클래스 정의 내에서 멤버 함수를 선언할 때 매개변수 목록에 'this'를 넣지 않지만 이는 입력을 절약하기 위해 언어 디자이너가 제공한 선물일 뿐입니다.대신 추가 'this' 매개변수를 자동으로 가져오는 것을 선택 해제하려면 멤버 함수가 정적임을 지정해야 합니다.C++ 컴파일러가 위 예제를 C 코드로 변환한 경우(원래 C++ 컴파일러는 실제로 그런 방식으로 작동함) 아마도 다음과 같이 작성될 것입니다.

struct A {
    int data;
};

void a_init(A* to_init)
{
    to_init->data = 0;
}

void a_foo(A* this, int addToData)
{ 
    this->data += addToData;
}

...

A an_a_object;
a_init(0); // Before constructor call was implicit
a_foo(&an_a_object, 5); // Used to be an_a_object.foo(5);

귀하의 예로 돌아가면 이제 명백한 문제가 있습니다.'Init'은 하나의 매개변수를 취하는 함수에 대한 포인터를 원합니다.그러나 &CLoggersInfra::RedundencyManagerCallBack은 두 개의 매개변수, 즉 일반 매개변수와 비밀 'this' 매개변수를 사용하는 함수에 대한 포인터입니다.따라서 여전히 컴파일러 오류가 발생하는 이유는 다음과 같습니다.Python을 사용해 본 적이 있다면 이러한 혼란이 모든 멤버 함수에 'self' 매개변수가 필요한 이유입니다.

이를 처리하는 자세한 방법은 원하는 인스턴스에 대한 포인터를 보유하고 매개변수를 사용하는 'run' 또는 'execute'(또는 '()' 연산자를 오버로드함)와 같은 멤버 함수가 있는 특수 개체를 만드는 것입니다. 멤버 함수의 경우 저장된 인스턴스의 해당 매개변수를 사용하여 멤버 함수를 호출하기만 하면 됩니다.그러나 이렇게 하려면 원시 함수 포인터가 아닌 특수 객체를 사용하기 위해 'Init'를 변경해야 하며 Init가 다른 사람의 코드인 것처럼 들립니다.그리고 이 문제가 발생할 때마다 특별한 클래스를 만들면 코드가 너무 커지게 됩니다.

이제 마지막으로 좋은 솔루션인 Boost::bind 및 Boost::function에 대한 문서는 여기에서 찾을 수 있습니다.

부스트::문서 바인딩, 부스트::함수 문서

Boost::bind를 사용하면 함수와 해당 함수에 대한 매개변수를 가져와 해당 매개변수가 제자리에 '잠긴' 새 함수를 만들 수 있습니다.따라서 두 개의 정수를 추가하는 함수가 있는 경우 Boost::bind를 사용하여 매개변수 중 하나가 5로 고정되는 새 함수를 만들 수 있습니다.이 새로운 함수는 하나의 정수 매개변수만 취하고 항상 5를 추가합니다.이 기술을 사용하면 숨겨진 'this' 매개변수를 특정 클래스 인스턴스로 '고정'하고 원하는 대로 매개변수 하나만 취하는 새 함수를 생성할 수 있습니다(숨겨진 매개변수는 항상 첫 번째 매개변수가 있고 일반 매개변수가 그 뒤에 순서대로 옵니다).예를 들어 Boost::bind 문서를 살펴보세요. 심지어 멤버 함수에 사용하는 방법에 대해서도 구체적으로 설명합니다.기술적으로 std::bind1st라는 표준 함수도 사용할 수 있지만 Boost::bind가 더 일반적입니다.

물론, 딱 하나의 문제가 더 있습니다.Boost::bind는 여러분에게 멋진 Boost::function을 제공할 것입니다. 그러나 이는 기술적으로 Init이 원하는 것과 같은 원시 함수 포인터가 아닙니다.고맙게도 Boost는 StackOverflow에 문서화된 대로 Boost::function을 원시 포인터로 변환하는 방법을 제공합니다. 여기.이를 구현하는 방법은 이 답변의 범위를 벗어납니다. 하지만 그것도 흥미롭습니다.

이것이 터무니없이 어려워 보이더라도 걱정하지 마십시오. 귀하의 질문은 C++의 어두운 구석과 교차하며, Boost::bind는 일단 배우고 나면 매우 유용합니다.

C++11 업데이트:이제 Boost::bind 대신 'this'를 캡처하는 람다 함수를 사용할 수 있습니다.이는 기본적으로 컴파일러가 동일한 것을 생성하도록 하는 것입니다.

이 답변은 위의 의견에 대한 답변이며 VisureStudio 2008과 함께 작동하지 않지만 최신 컴파일러에서 선호해야합니다.


한편 더 이상 빈 공간 포인터를 사용할 필요가 없으며 그 이후로 부스트가 필요하지 않습니다. std::bind 그리고 std::function 사용 가능합니다. 하나 이점 (무효 포인터와 비교하여)은 반환 유형을 사용하고 인수가 명시 적으로 명시되어 있기 때문에 유형 안전입니다. std::function:

// std::function<return_type(list of argument_type(s))>
void Init(std::function<void(void)> f);

그런 다음 기능 포인터를 만들 수 있습니다 std::bind 그리고 그것을 초기로 전달하십시오.

auto cLoggersInfraInstance = CLoggersInfra();
auto callback = std::bind(&CLoggersInfra::RedundencyManagerCallBack, cLoggersInfraInstance);
Init(callback);

완전한 예 사용을 위해 std::bind 회원, 정적 멤버 및 비 멤버 기능과 함께 :

#include <functional>
#include <iostream>
#include <string>

class RedundencyManager // incl. Typo ;-)
{
public:
    // std::function<return_type(list of argument_type(s))>
    std::string Init(std::function<std::string(void)> f) 
    {
        return f();
    }
};

class CLoggersInfra
{
private:
    std::string member = "Hello from non static member callback!";

public:
    static std::string RedundencyManagerCallBack()
    {
        return "Hello from static member callback!";
    }

    std::string NonStaticRedundencyManagerCallBack()
    {
        return member;
    }
};

std::string NonMemberCallBack()
{
    return "Hello from non member function!";
}

int main()
{
    auto instance = RedundencyManager();

    auto callback1 = std::bind(&NonMemberCallBack);
    std::cout << instance.Init(callback1) << "\n";

    // Similar to non member function.
    auto callback2 = std::bind(&CLoggersInfra::RedundencyManagerCallBack);
    std::cout << instance.Init(callback2) << "\n";

    // Class instance is passed to std::bind as second argument.
    // (heed that I call the constructor of CLoggersInfra)
    auto callback3 = std::bind(&CLoggersInfra::NonStaticRedundencyManagerCallBack,
                               CLoggersInfra()); 
    std::cout << instance.Init(callback3) << "\n";
}

가능한 출력 :

Hello from non member function!
Hello from static member callback!
Hello from non static member callback!

또한 사용합니다 std::placeholders 콜백에 인수를 동적으로 전달할 수 있습니다 (예 : 사용을 가능하게합니다. return f("MyString"); 안에 Init f에 문자열 매개 변수가있는 경우).

논쟁이하는 일 Init 가져가다? 새로운 오류 메시지는 무엇입니까?

C ++의 메소드 포인터는 사용하기가 약간 어렵습니다. 메소드 포인터 자체 외에도 인스턴스 포인터도 제공해야합니다 (경우에 따라 this). 아마도 Init 그것을 별도의 주장으로 기대합니까?

A pointer to a class member function is not the same as a pointer to a function. A class member takes an implicit extra argument (the this pointer), and uses a different calling convention.

If your API expects a nonmember callback function, that's what you have to pass to it.

Is m_cRedundencyManager able to use member functions? Most callbacks are set up to use regular functions or static member functions. Take a look at this page at C++ FAQ Lite for more information.

Update: The function declaration you provided shows that m_cRedundencyManager is expecting a function of the form: void yourCallbackFunction(int, void *). Member functions are therefore unacceptable as callbacks in this case. A static member function may work, but if that is unacceptable in your case, the following code would also work. Note that it uses an evil cast from void *.


// in your CLoggersInfra constructor:
m_cRedundencyManager->Init(myRedundencyManagerCallBackHandler, this);

// in your CLoggersInfra header:
void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr);

// in your CLoggersInfra source file:
void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr)
{
    ((CLoggersInfra *)CLoggersInfraPtr)->RedundencyManagerCallBack(i);
}

I can see that the init has the following override:

Init(CALLBACK_FUNC_EX callback_func, void * callback_parm)

where CALLBACK_FUNC_EX is

typedef void (*CALLBACK_FUNC_EX)(int, void *);

This question and answer from the C++ FAQ Lite covers your question and the considerations involved in the answer quite nicely I think. Short snippet from the web page I linked:

Don’t.

Because a member function is meaningless without an object to invoke it on, you can’t do this directly (if The X Window System was rewritten in C++, it would probably pass references to objects around, not just pointers to functions; naturally the objects would embody the required function and probably a whole lot more).

Necromancing.
I think the answers to date are a little unclear.

Let's make an example:

Supposed you have an array of pixels (array of ARGB int8_t values)

// A RGB image
int8_t* pixels = new int8_t[1024*768*4];

Now you want to generate a PNG. To do so, you call the function toJpeg

bool ok = toJpeg(writeByte, pixels, width, height);

where writeByte is a callback-function

void writeByte(unsigned char oneByte)
{
    fputc(oneByte, output);
}

The problem here: FILE* output has to be a global variable.
Very bad if you're in a multithreaded environment (e.g. a http-server).

So you need some way to make output a non-global variable, while retaining the callback signature.

The immediate solution that springs into mind is a closure, which we can emulate using a class with a member function.

class BadIdea {
private:
    FILE* m_stream;
public:
    BadIdea(FILE* stream)  {
        this->m_stream = stream;
    }

    void writeByte(unsigned char oneByte){
            fputc(oneByte, this->m_stream);
    }

};

And then do

FILE *fp = fopen(filename, "wb");
BadIdea* foobar = new BadIdea(fp);

bool ok = TooJpeg::writeJpeg(foobar->writeByte, image, width, height);
delete foobar;
fflush(fp);
fclose(fp);

However, contrary to expectations, this does not work.

The reason is, C++ member functions are kinda implemented like C# extension functions.

So you have

class/struct BadIdea
{
    FILE* m_stream;
}

and

static class BadIdeaExtensions
{
    public static writeByte(this BadIdea instance, unsigned char oneByte)
    {
         fputc(oneByte, instance->m_stream);
    }

}

So when you want to call writeByte, you need pass not only the address of writeByte, but also the address of the BadIdea-instance.

So when you have a typedef for the writeByte procedure, and it looks like this

typedef void (*WRITE_ONE_BYTE)(unsigned char);

And you have a writeJpeg signature that looks like this

bool writeJpeg(WRITE_ONE_BYTE output, uint8_t* pixels, uint32_t 
 width, uint32_t height))
    { ... }

it's fundamentally impossible to pass a two-address member function to a one-address function pointer (without modifying writeJpeg), and there's no way around it.

The next best thing that you can do in C++, is using a lambda-function:

FILE *fp = fopen(filename, "wb");
auto lambda = [fp](unsigned char oneByte) { fputc(oneByte, fp);  };
bool ok = TooJpeg::writeJpeg(lambda, image, width, height);

However, because lambda is doing nothing different, than passing an instance to a hidden class (such as the "BadIdea"-class), you need to modify the signature of writeJpeg.

The advantage of lambda over a manual class, is that you just need to change one typedef

typedef void (*WRITE_ONE_BYTE)(unsigned char);

to

using WRITE_ONE_BYTE = std::function<void(unsigned char)>; 

And then you can leave everything else untouched.

You could also use std::bind

auto f = std::bind(&BadIdea::writeByte, &foobar);

But this, behind the scene, just creates a lambda function, which then also needs the change in typedef.

So no, there is no way to pass a member function to a method that requires a static function-pointer.

But lambdas are the easy way around, provided that you have control over the source.
Otherwise, you're out of luck.
There's nothing you can do with C++.

Note:
std::function requires #include <functional>

However, since C++ allows you to use C as well, you can do this with libffcall in plain C, if you don't mind linking a dependency.

Download libffcall from GNU (at least on ubuntu, don't use the distro-provided package - it is broken), unzip.

./configure
make
make install

gcc main.c -l:libffcall.a -o ma

main.c:

#include <callback.h>

// this is the closure function to be allocated 
void function (void* data, va_alist alist)
{
     int abc = va_arg_int(alist);

     printf("data: %08p\n", data); // hex 0x14 = 20
     printf("abc: %d\n", abc);

     // va_start_type(alist[, return_type]);
     // arg = va_arg_type(alist[, arg_type]);
     // va_return_type(alist[[, return_type], return_value]);

    // va_start_int(alist);
    // int r = 666;
    // va_return_int(alist, r);
}



int main(int argc, char* argv[])
{
    int in1 = 10;

    void * data = (void*) 20;
    void(*incrementer1)(int abc) = (void(*)()) alloc_callback(&function, data);
    // void(*incrementer1)() can have unlimited arguments, e.g. incrementer1(123,456);
    // void(*incrementer1)(int abc) starts to throw errors...
    incrementer1(123);
    // free_callback(callback);
    return EXIT_SUCCESS;
}

And if you use CMake, add the linker library after add_executable

add_library(libffcall STATIC IMPORTED)
set_target_properties(libffcall PROPERTIES
        IMPORTED_LOCATION /usr/local/lib/libffcall.a)
target_link_libraries(BitmapLion libffcall)

or you could just dynamically link libffcall

target_link_libraries(BitmapLion ffcall)

Note:
You might want to include the libffcall headers and libraries, or create a cmake project with the contents of libffcall.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top