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

123

Вопрос в следующем: рассмотрите этот фрагмент кода:

#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's aClass::testв качестве аргумента function1? Я застрял в этом.

Я хочу получить доступ к члену класса.

Хорхе Лейтао
источник
1
Взгляните на этот ответ stackoverflow.com/questions/2402579/…, а также на этот часто задаваемый вопрос по
amdn
16
Это абсолютно не дубликат (по крайней мере, не связанный с конкретным вопросом). Этот вопрос касается того, как объявить член, который является указателем на функцию; это о том, как передать указатель на нестатическую функцию-член в качестве параметра.
CarLuva

Ответы:

145

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

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

а не тот тип, который вы пытаетесь использовать

void (*)(int, int)

Один из подходов может заключаться в создании функции-члена, и staticв этом случае она не требует вызова какого-либо объекта, и вы можете использовать ее с типом void (*)(int, int).

Если вам нужно получить доступ к любому нестатическому члену вашего класса, и вам нужно придерживаться указателей функций, например, поскольку функция является частью интерфейса C, ваш лучший вариант - всегда передавать a 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 *, а затем вызывает функцию члена», или поделитесь полезной ссылкой на него? Спасибо
Хорхе Лейтао
Думаю, я понял. (Я отредактировал ваш пост) Спасибо за объяснение и пример, очень полезный. Просто для подтверждения: для каждой функции-члена, на которую я хочу указать, я должен создать пересылку. Правильно?
Хорхе Лейтао,
Ну да, вроде как. В зависимости от того, насколько эффективно вы используете шаблоны, вы можете обойтись без создания шаблонов пересылки, которые могут работать с различными классами и функциями-членами. Я бы подумал, как это сделать - это отдельная функция ;-)
Дитмар Кюль
1
Мне не нравится этот ответ, потому что он использует void*, а это означает, что вы можете получить очень неприятные ошибки, потому что он больше не проверяется типом.
Superlokkus
@Superlokkus: не могли бы вы рассказать нам об альтернативе?
Дитмар Кюль,
91

Ответ @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;
}
Мэтт Филлипс
источник
1
Является ли void function1(std::function<void(int, int)>)правильно?
Deqing
2
Вам нужно дать аргументу функции имя переменной, а затем имя переменной - это то, что вы фактически передаете. Итак: void function1(std::function<void(int, int)> functionToCall)а потом functionToCall(1,1);. Я попытался отредактировать ответ, но кто-то отклонил его как бессмысленный по какой-то причине. Посмотрим, получит ли он когда-нибудь поддержку.
Dorky Engineer
1
@DorkyEngineer Это довольно странно, я думаю, вы правы, но я не знаю, как эта ошибка могла так долго оставаться незамеченной. Во всяком случае, сейчас я отредактировал ответ.
Мэтт Филлипс,
2
Я нашел этот пост, в котором говорится, что std :: function сильно снижает производительность.
Кевин
2
@kevin, вы можете пересмотреть свой комментарий, поскольку ответы на этот пост показали недостаток в тесте.
Хорхе Лейтао,
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 01
Я думал , что functionв 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 03
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;
}
mathengineer
источник
Не только решить главный вопрос: «Как я могу использовать aClass :: test в качестве аргумента функции 1?» но также рекомендуется избегать изменения частных переменных класса с помощью кода, внешнего по отношению к классу
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
HLK
источник
0

Если вы на самом деле не нужно использовать экземпляр a (т.е. вы можете сделать его статическим , как @mathengineer «s ответ ) вы можете просто передать без захвата лямбда. (которые распадаются на указатель функции)


#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строительство требует больших затрат или имеет побочный эффект, это может быть не лучшим способом.

яблоко яблоко
источник
-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

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