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

Вопрос следующий: рассмотрим этот кусок кода:

#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()
{
    aClass a;

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

Как я могу использовать a aClass::test в качестве аргумента function1? Я хотел бы получить доступ к члену класса.


person Jorge Leitao    schedule 30.09.2012    source источник
comment
Взгляните на этот ответ stackoverflow.com/ questions/2402579/, а также этот FAQ по C++ parashift.com /c++-faq/pointers-to-members.html   -  person amdn    schedule 30.09.2012
comment
Это абсолютно не дубликат (по крайней мере, не связанного конкретного вопроса). Этот вопрос касается того, как объявить член, который является указателем на функцию; это о том, как передать указатель на нестатическую функцию-член в качестве параметра.   -  person CarLuva    schedule 23.07.2014


Ответы (9)


Нет ничего плохого в использовании указателей на функции. Однако указатели на нестатические функции-члены не похожи на обычные указатели функций: функции-члены необходимо вызывать для объекта, который передается функции в качестве неявного аргумента. Подпись вашей функции-члена выше, таким образом,

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, nullptr);
    foo object;
    somefunction(&forwarder, &object);
}
person Dietmar Kühl    schedule 30.09.2012
comment
Хорошо, мне нравится этот ответ! Не могли бы вы указать, что вы имеете в виду под вызовом вашего члена через функцию пересылки, которая получает объект из пустоты *, а затем вызывает функцию-член, или поделитесь полезной ссылкой на нее? Спасибо - person Jorge Leitao; 30.09.2012
comment
Думаю, я понял. (Я отредактировал ваш пост) Спасибо за объяснение и пример, очень полезно. Просто для подтверждения: для каждой функции-члена, которую я хочу указать, я должен создать пересылку. Верно? - person Jorge Leitao; 30.09.2012
comment
Ну да, вроде. В зависимости от того, насколько эффективно вы используете шаблоны, вам может сойти с рук создание шаблонов переадресации, которые могут работать с различными классами и функциями-членами. Как это сделать, я думаю, будет отдельная функция ;-) - person Dietmar Kühl; 30.09.2012
comment
Мне не нравится этот ответ, потому что он использует void*, что означает, что вы можете получить очень неприятные ошибки, потому что он больше не проверяется. - person Superlokkus; 22.09.2015
comment
@Superlokkus: не могли бы вы рассказать нам об альтернативе? - person Dietmar Kühl; 22.09.2015
comment
DietmarKühl смотрите ответы PeterBecker и особенно @MattPhillips ниже - person Superlokkus; 22.09.2015
comment
@Superlokkus: обратите внимание, что в моем ответе упоминаются оба этих подхода! Он просто подробно объясняет, как на самом деле танцевать, вызывая функцию-член, поскольку это не очевидно. - person Dietmar Kühl; 22.09.2015
comment
@DietmarKühl Но ваш ответ A) упоминает безопасные подходы только после небезопасного очень незаметным образом B) не упоминает потенциально опасные небезопасные последствия вашего void*solution C) предлагает вам лучший вариант - всегда передавать пустоту * для вашей функции и static_cast<foo*>(context)->member(i0, i1); это единственное и безопасное решение, которое не является. Мы все знаем проблемы, вызванные обходом системы типов, и ваш ответ ИМХО поощряет это неправильное использование. - person Superlokkus; 22.09.2015
comment
Как будет выглядеть шаблон, который принимает функции и нестатические функции-члены? Я борюсь ... Ура! - person germannp; 20.06.2016
comment
@qiv: кажется, это отдельный вопрос ... Простой ответ заключается в том, что вы просто передаете template <typename Fun> void f(Fun fun /*...*/) и в реализации f() используете объект fun с подходящим помощником (например, std::bind() и вызываете функцию через помощник с подходящим аргументы. - person Dietmar Kühl; 20.06.2016

Ответ @Pete Becker в порядке, но вы также можете сделать это, не передавая экземпляр class в качестве явного параметра для function1 в C++ 11:

#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;
}
person Matt Phillips    schedule 30.09.2012
comment
Правильно ли void function1(std::function<void(int, int)>)? - person Deqing; 09.04.2015
comment
Вам нужно дать аргументу функции имя переменной, а затем имя переменной — это то, что вы фактически передаете. Итак: void function1(std::function<void(int, int)> functionToCall), а затем functionToCall(1,1);. Я пытался отредактировать ответ, но кто-то отклонил его как бессмысленный по какой-то причине. Посмотрим, проголосуют ли за него в какой-то момент. - person Dorky Engineer; 09.04.2015
comment
@DorkyEngineer Это довольно странно, я думаю, вы должны быть правы, но я не знаю, как эта ошибка могла так долго оставаться незамеченной. Во всяком случае, я отредактировал ответ сейчас. - person Matt Phillips; 09.04.2015
comment
Я нашел этот сообщение, в котором говорилось, что std::function серьезно снижает производительность. - person kevin; 29.06.2015
comment
@kevin, возможно, вы захотите пересмотреть свой комментарий, так как ответы на этот пост показали ошибку в тесте. - person Jorge Leitao; 03.01.2020
comment
Отличный ответ - это именно то, что я искал. Однако есть одна небольшая деталь - я считаю, что это должно быть: auto fp = std::bind(&aClass::test, &a, _1, _2); Если вы не передаете экземпляр класса в качестве ссылки, похоже, что компилятор (по крайней мере, MSVC2019) пытается его переместить, что, вероятно, не то, что вы хотите. См. также пример здесь (auto f3 = ...). - person James S.; 11.04.2020

Указатель на функцию-член отличается от указателя на функцию. Чтобы использовать функцию-член через указатель, вам нужен указатель на нее (очевидно) и объект для его применения. Таким образом, подходящей версией 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);
person Pete Becker    schedule 30.09.2012
comment
Спасибо большое. Я только что узнал, что здесь учитываются скобки: function1(&(aClass::test), a) работает с MSVC2013, но не с gcc. gcc нуждается в & непосредственно перед именем класса (что меня сбивает с толку, потому что оператор & берет адрес функции, а не класса) - person kritzel_sw; 01.03.2016
comment
Я думал, что function в void (aClass::*function)(int, int) был типом, потому что это тип в typedef void (aClass::*function)(int, int). - person Olumide; 09.11.2017
comment
@Olumide — typedef int X; определяет тип; int X; создает объект. - person Pete Becker; 09.11.2017

С 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(); объявляет функцию).

person Lightness Races in Orbit    schedule 10.08.2018

Я задал аналогичный вопрос (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()});

где вы создаете лямбда, которая вызывает объект, захватив его по ссылке

person Nicola Bertelloni    schedule 08.11.2018
comment
Учитывая, что это помечено как CPP, я думаю, что это самое чистое решение. Мы могли бы использовать постоянную ссылку на std::function в draw вместо того, чтобы копировать ее при каждом вызове. - person Sohaib; 28.08.2019
comment
В этом примере не следует использовать заполнитель, так как функция не принимает никаких параметров. - person kroiz; 03.09.2019

Не уверен, почему это невероятно простое решение было пропущено:

#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
person Asad-ullah Khan    schedule 28.03.2020

Я сделал функцию-член статической, и все работает:

#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;
}
person mathengineer    schedule 15.12.2018
comment
Не только решить основной вопрос Как я могу использовать класс aClass::test в качестве аргумента для функции1? но также рекомендуется избегать изменения частных переменных класса с использованием кода, внешнего по отношению к классу. - person mathengineer; 15.12.2018

Если вам на самом деле не нужно использовать экземпляр 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);}); 
}

Wandbox


примечание: если aClass дорого построить или имеет побочный эффект, это не лучший способ.

person apple apple    schedule 15.07.2020

Теперь можешь перестать биться головой. Вот оболочка для функции-члена для поддержки существующих функций, принимающих простые функции 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

person Necktwi    schedule 07.08.2018
comment
Таким образом, вместо использования std::bind или лямбда для переноса экземпляра вы полагаетесь на глобальную переменную. Я не вижу никаких преимуществ этого подхода по сравнению с другими ответами. - person super; 07.08.2018
comment
@super, другие ответы не могут вызывать существующие функции, используя простые функции C в качестве аргументов. - person Necktwi; 07.08.2018
comment
Вопрос в том, как вызвать функцию-член. Передача бесплатной функции уже работает для ОП в вопросе. Вы тоже ничего не пропускаете. Здесь только жестко закодированные функции и глобальный указатель Foo_. Как это масштабируется, если вы хотите вызвать другую функцию-член? Вам придется переписать базовые функции или использовать разные для каждой цели. - person super; 07.08.2018