Как я могу передать функцию-член класса в качестве обратного вызова?


Я использую API, который требует от меня передать указатель функции в качестве обратного вызова. Я пытаюсь использовать этот API из моего класса, но я получаю ошибки компиляции.

вот что я сделал из моего конструктора:

m_cRedundencyManager->Init(this->RedundencyManagerCallBack);

это не компиляции я получаю следующую ошибку:

Ошибка 8 Ошибка C3867: 'CLoggersInfra:: RedundencyManagerCallBack': вызов функции отсутствует список аргументов; используйте '&CLoggersInfra::RedundencyManagerCallBack ' для создания указатель на элемент

я попробовал предложение использовать &CLoggersInfra::RedundencyManagerCallBack - не работает для меня.

любые предложения / объяснения для этого??

Я использую VS2008.

спасибо!!

8   51   2008-12-30 16:35:35

8 ответов:

Как вы теперь показали, ваша функция init принимает функцию, не являющуюся членом. так что сделайте это так:

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

и вызовите Init с

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

это работает, потому что указатель функции на статическую функцию-член не является указателем функции-члена и поэтому может обрабатываться как просто указатель на свободную функцию.

Это простой вопрос, но ответ на удивление сложным. Короткий ответ - вы можете сделать то, что вы пытаетесь сделать с 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 

сколько параметров принимает A::foo? Обычно мы бы сказали 1. Но под капотом, ФУ Действительно занимает 2. Глядя на определение A::foo, ему нужен конкретный экземпляр A, чтобы указатель "this" был значимым (компилятор должен знать, что такое "this"). То, как вы обычно указываете, что вы хотите, чтобы " это " было через синтаксис MyObject.MyMemberFunction (). Но это всего лишь синтаксис сахар для передачи адреса MyObject в качестве первого параметра в MyMemberFunction. Аналогично, когда мы объявляем функции-члены внутри определений классов, мы не помещаем " это " в список параметров, но это просто подарок от языковых дизайнеров для сохранения ввода. Вместо этого вы должны указать, что функция-член является статическим отказаться от автоматически получают этот параметр. Если компилятор 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, такая путаница-это то, почему "я" параметр необходим для всех функций-членов).

подробный способ обработки этого заключается в создании специального объекта, который содержит указатель на нужный экземпляр и имеет функцию-член, называемую чем-то вроде " run " или "execute" (или перегружает оператор " ()"), который принимает параметры для функции-члена и просто вызывает функцию-член с этими параметрами на сохраненном экземпляре. Но это потребует от вас изменить "Init", чтобы взять ваш специальный объект, а не сырой указатель на функцию, и это звучит как Init-это чужой код. И создание специального класса для каждого раза, когда эта проблема возникает, приведет к раздуванию кода.

Итак, теперь, наконец, хорошее решение, boost:: bind и boost::function, документацию для каждого вы можете найти здесь:

boost:: bind docs, boost:: function docs

boost:: bind позволит вам взять функцию и параметр для этой функции и создать новую функцию где этот параметр "заблокирован" на месте. Поэтому, если у меня есть функция, которая добавляет два целых числа, я могу использовать boost::bind, чтобы создать новую функцию, где один из параметров заблокирован, чтобы сказать 5. Эта новая функция будет принимать только один целочисленный параметр, и всегда будет добавлять 5 специально к нему. Используя этот метод, вы можете "заблокировать" скрытый параметр "this", чтобы быть конкретным экземпляром класса, и создать новую функцию, которая принимает только один параметр, как вы хотите (обратите внимание, что скрытый параметр всегда первый параметр, и нормальные параметры приходят в порядок после него). Посмотрите на boost:: bind docs для примеров, они даже специально обсуждают его использование для функций-членов. Технически существует стандартная функция std::bind1st, которую вы также можете использовать, но boost:: bind является более общей.

конечно, есть еще один подвох. boost:: bind сделает хороший импульс:: функция для вас, но это все еще технически не сырой указатель функции, как Инит, наверное, хочет. К счастью, boost предоставляет способ конвертировать boost:: function в необработанные указатели, как описано в StackOverflow здесь. Как он реализует это выходит за рамки этого ответа, хотя это тоже интересно.

Не волнуйтесь, если это кажется смехотворно трудно-ваш вопрос пересекает несколько темных углов C++, и boost::bind невероятно полезен, как только вы его узнаете.

этот ответ является ответом на комментарий выше и не работает с VisualStudio 2008, но следует отдать предпочтение с более поздними компиляторами.


между тем вам больше не нужно использовать указатель void, а также нет необходимости в усилении с std::bind и std::function доступны. один преимуществом (по сравнению с указателями void) является безопасность типа, так как возвращаемый тип и аргументы явно указаны с помощью std::function:

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

затем вы можете создать указатель на функцию std::bind и передайте его Init:

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"); in Init если f имеет строку параметр.)

какой аргумент делает Init взять? Что такое новое сообщение об ошибке?

указатели на методы в C++ немного сложны в использовании. Помимо самого указателя метода, вам также необходимо предоставить указатель экземпляра (в вашем случае this). Может быть Init ожидает его в качестве отдельного аргумента?

и m_cRedundencyManager возможность использовать функции-члены? Большинство обратных вызовов настроены на использование регулярных функций или статических функций-членов. Взгляните на на этой странице на C++ FAQ Lite для получения дополнительной информации.

обновление: объявление функции, которые вы предоставили, показывает, что m_cRedundencyManager ожидает функцию вида:void yourCallbackFunction(int, void *). Поэтому функции-члены неприемлемы в качестве обратных вызовов в этом случае. Статическая функция-член мая работа, но если это так недопустимо в вашем случае, следующий код также будет работать. Обратите внимание, что он использует злой бросок от 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);
}

указатель на функцию-член класса-это не то же самое, что указатель на функцию. Член класса принимает неявный дополнительный аргумент (этой указатель), и использует другое соглашение о вызовах.

Если ваш API ожидает функцию обратного вызова, не являющуюся членом, это то, что вы должны передать ему.

Я вижу, что init имеет следующее переопределение:

Init(CALLBACK_FUNC_EX callback_func, void * callback_parm)

где CALLBACK_FUNC_EX является typedef void (*CALLBACK_FUNC_EX)(int, void *);

этой вопрос-ответ С C++ FAQ Lite охватывает ваш вопрос и соображения, связанные с ответом довольно хорошо, я думаю. Короткий фрагмент с веб-страницы, которую я связал:

нет.

потому что функция-член бессмысленна без объекта для вызова он включен, вы не можете сделать это напрямую (если система X Window была переписанный на C++, он, вероятно, будет передавать ссылки на объекты вокруг, не только указатели на функции; естественно, объекты будут воплощать необходимая функция и, вероятно, многое другое).