Як я можу передати функцію члена, де очікується вільна функція?


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«S в 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).

Якщо вам потрібно отримати доступ до будь-якого нестатичного члена вашого класу, і вам потрібно дотримуватися функціональних покажчиків, наприклад, оскільки функція є частиною інтерфейсу С, найкращим варіантом є завжди передавати функцію 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);
}

Гаразд, мені подобається ця відповідь! Чи можете ви вкажіть, що ви маєте на увазі під "викликом свого члена через функцію переадресації, яка отримує об'єкт з пустоти *, а потім викликає функцію члена", або поділитися корисним посиланням на нього? Спасибі
Хорхе Лейтао

Я думаю, що я це отримав. (Я відредагував ваше повідомлення) Дякую за пояснення та приклад, дуже корисні. Просто для підтвердження: для кожної функції учасника, яку я хочу вказати, я повинен зробити переслати. Правильно?
Хорхе Лейтао

Ну так, начебто. Залежно від ефективності використання шаблонів, ви можете піти від створення шаблонів переадресації, які можуть працювати з різними класами та функціями членів. Як зробити це було б окремою функцією, я б подумав ;-)
Дітмар Кюл

1
Я не люблю цю відповідь, оскільки вона використовує void*, а це означає, що ви можете отримати дуже неприємні помилки, тому що це більше не набирається.
Суперлоккус

@Superlokkus: чи можете ви просвітите нас альтернативою?
Дітмар Кюль

88

@Pete Беккер відповідь прекрасний, але ви також можете це зробити, не передаючи 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)>)правильно?
Декінг

2
Вам потрібно дати аргументу функції ім'я змінної, а потім ім'я змінної - це те, що ви фактично передаєте. Отже: void function1(std::function<void(int, int)> functionToCall)і тоді functionToCall(1,1);. Я спробував відредагувати відповідь, але хтось відкинув її як не має сенсу з якихось причин. Ми побачимо, чи не буде прийнято рішення в якийсь момент.
Інженер Доркі

1
@DorkyEngineer Це досить дивно, я думаю, ви повинні мати рацію, але я не знаю, як ця помилка могла так довго пройти непомітно. У всякому разі, я зараз відредагував відповідь.
Метт Філліпс

2
У цьому дописі я виявив, що стд :: функція суворо карає.
кевін

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

Я думав , що functionв void (aClass::*function)(int, int)був типу, з - за його типу в typedef void (aClass::*function)(int, int).
Олумід

@Olumide - typedef int X;визначає тип; int X;створює об’єкт.
Піт Бекер

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);
    });
}

( live demo )

Зауважте також, що я виправив ваше розбите визначення об'єкта ( aClass a();оголошує функцію).


2

Я задав подібне запитання ( C ++ openframeworks, що передає порожнечу від інших класів ), але відповідь, яку я знайшов, була більш зрозумілою, тому тут пояснення для майбутніх записів:

простіше використовувати std :: функцію, як у:

 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::functionв, 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;
}

Не вирішуйте лише головне питання "Як я можу використовувати тест aClass ::: як аргумент до функції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

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

Ви можете перестати стукати головою вже зараз. Ось обгортка функції-члена, яка підтримує існуючі функції, беручи в якості аргументів прості функції С. 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 або lambda, щоб обернути екземпляр, ви покладаєтесь на глобальну змінну. Я не бачу жодної переваги такого підходу порівняно з іншими відповідями.
супер

@super, Інші відповіді не можуть викликати існуючі функції, беручи в Cякості аргументів прості функції.
Necktwi

2
Питання в тому, як викликати функцію члена. Перехід у вільну функцію вже працює для ОП у питанні. Ви також нічого не переходите. Тут є лише жорстко закодовані функції та глобальний покажчик Foo_. Яким би був цей масштаб, якщо ви хочете викликати функцію іншого члена? Вам доведеться переписати основні функції або використовувати різні для кожної цілі.
супер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.