Вызов указателя функции C ++ на конкретный экземпляр объекта - PullRequest
13 голосов
/ 30 сентября 2008

У меня есть указатель на функцию, определяемый как:

typedef void (*EventFunction)(int nEvent);

Есть ли способ обработать эту функцию с помощью конкретного экземпляра объекта C ++?

class A
{
private:
    EventFunction handler;

public:
    void SetEvent(EventFunction func) { handler = func; }

    void EventOne() { handler(1); }
};

class B
{
private:
    A a;
public:
    B() { a.SetEvent(EventFromA); }  // What do I do here?

    void EventFromA(int nEvent) { // do stuff }
};

Редактировать: Орион указал опции, которые предлагает Boost, такие как:

boost::function<int (int)> f;
X x;
f = std::bind1st(
      std::mem_fun(&X::foo), &x);
f(5); // Call x.foo(5)

К сожалению, Boost не подходит для меня. Существует ли какая-нибудь «карри» функция, которая может быть написана на C ++, которая будет выполнять такой вид переноса указателя на функцию-член в обычный указатель функции?

Ответы [ 10 ]

11 голосов
/ 30 сентября 2008

Вы можете использовать указатели функций для индексации в vtable данного экземпляра объекта. Это называется указатель на функцию-член . Ваш синтаксис должен измениться, чтобы использовать операторы ". *" И "& ::":

class A;
class B;
typedef void (B::*EventFunction)(int nEvent)

и затем:

class A
{
private:
    EventFunction handler;

public:
    void SetEvent(EventFunction func) { handler = func; }

    void EventOne(B* delegate) { ((*delegate).*handler)(1); } // note: ".*"
};

class B
{
private:
    A a;
public:
    B() { a.SetEvent(&B::EventFromA); } // note: "&::"

    void EventFromA(int nEvent) { /* do stuff */ }
};
11 голосов
/ 30 сентября 2008

Убегите от необработанных указателей на функции C ++ и используйте std::function.

Вы можете использовать boost::function, если вы используете старый компилятор, такой как Visual Studio 2008, который не поддерживает C ++ 11.
boost:function и std::function - это одно и то же - они добавили немного поддержки в библиотеку std для C ++ 11.

Примечание: вы, возможно, захотите прочитать документацию по функции повышения вместо Microsoft, поскольку это легче понять

9 голосов
/ 30 сентября 2008

Я очень рекомендую отличную библиотеку Дона Клагстона FastDelegate. Он предоставляет все то, что вы ожидаете от реального делегата, и в большинстве случаев сводится к нескольким инструкциям ASM. Прилагаемая статья также хорошо читается об указателях на функции-члены.

http://www.codeproject.com/KB/cpp/FastDelegate.aspx

4 голосов
/ 30 сентября 2008

Вы можете найти C ++ FAQ от Marshall Cline полезным для того, чего вы пытаетесь достичь.

2 голосов
/ 30 сентября 2008

Если вы взаимодействуете с библиотекой C, то вы не можете использовать функцию-член класса, не используя что-то вроде boost::bind. Большинство библиотек C, которые принимают функцию обратного вызова, обычно также позволяют передавать дополнительный аргумент по вашему выбору (обычно типа void*), который вы можете использовать для начальной загрузки вашего класса, например:


class C
{
public:
  int Method1(void) { return 3; }
  int Method2(void) { return x; }

  int x;
};

// This structure will hold a thunk to
struct CCallback
{
  C *obj;  // Instance to callback on
  int (C::*callback)(void);  // Class callback method, taking no arguments and returning int
};

int CBootstrapper(CCallback *pThunk)
{
  // Call the thunk
  return ((pThunk->obj) ->* (pThunk->callback))( /* args go here */ );
}

void DoIt(C *obj, int (C::*callback)(void))
{
  // foobar() is some C library function that takes a function which takes no arguments and returns int, and it also takes a void*, and we can't change it
  struct CCallback thunk = {obj, callback};
  foobar(&CBootstrapper, &thunk);
}

int main(void)
{
  C c;
  DoIt(&c, &C::Method1);  // Essentially calls foobar() with a callback of C::Method1 on c
  DoIt(&c, &C::Method2);  // Ditto for C::Method2
}
2 голосов
/ 30 сентября 2008

Читайте о указателях на членов . Чтобы вызвать метод в производном классе, метод должен быть объявлен в базовом классе как виртуальный и переопределен в базовом классе, а указатель должен указывать на метод базового класса. Подробнее о указателях на виртуальных участников .

1 голос
/ 30 сентября 2008

Вы упоминаете, что повышение не вариант для вас, но у вас есть TR1 для вас?

TR1 предлагает объекты function, bind и mem_fn на основе библиотеки boost, и вы можете уже связать их с вашим компилятором. Это пока не стандартно, но по крайней мере два компилятора, которые я использовал недавно, имели его.

http://en.wikipedia.org/wiki/Technical_Report_1
http://msdn.microsoft.com/en-us/library/bb982702.aspx

1 голос
/ 30 сентября 2008

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

typedef void (*B::EventFunction)(int nEvent);

... и тогда все работает, как только вы вызываете функцию обратного вызова с объектом B. Но вы, вероятно, хотите иметь возможность вызывать функции вне B, в других классах, которые делают другие вещи. Это своего рода точка обратного вызова. Но теперь этот тип указывает на что-то определенно в B. Более привлекательные решения:

  • Сделать B базовым классом, а затем переопределить виртуальную функцию для каждого другого класса, который может быть вызван. Затем A сохраняет указатель на B вместо указателя на функцию. Гораздо чище.
  • Если вы не хотите связывать функцию с конкретным типом класса, даже с базовым классом (и я бы не стал вас винить), тогда я предлагаю вам сделать функцию, которая вызывается статической функцией: "static void EventFrom A(int nEvent);». Затем вы можете вызвать его напрямую, без объекта B. Но вы, вероятно, хотите, чтобы он вызывал конкретный экземпляр B (если B не является одиночным).
  • Так что, если вы хотите иметь возможность вызывать конкретный экземпляр B, но иметь возможность вызывать и не-B, тогда вам нужно передать что-то еще в вашу функцию обратного вызова, чтобы функция обратного вызова могла вызывать нужный объект. , Сделайте вашу функцию статической, как указано выше, и добавьте параметр void *, который вы сделаете указателем на B.

На практике вы видите два решения этой проблемы: специальные системы, в которых вы передаете пустоту * и событие, и иерархии с виртуальными функциями в базовом классе, такие как оконные системы

0 голосов
/ 30 сентября 2008

У меня есть набор классов для этой точной вещи, которые я использую в моей среде c ++.

http://code.google.com/p/kgui/source/browse/trunk/kgui.h

То, как я обращаюсь с этим, - каждая функция класса, которая может использоваться в качестве обратного вызова, нуждается в статической функции, которая связывает тип объекта с ней. У меня есть набор макросов, которые делают это автоматически. Он создает статическую функцию с тем же именем, за исключением префикса «CB_» и дополнительного первого параметра, который является указателем на объект класса.

Извлечение типов классов kGUICallBack и различных версий их шаблонов для обработки различных комбинаций параметров.

#define CALLBACKGLUE(classname , func) static void CB_ ## func(void *obj) {static_cast< classname *>(obj)->func();}
#define CALLBACKGLUEPTR(classname , func, type) static void CB_ ## func(void *obj,type *name) {static_cast< classname *>(obj)->func(name);}
#define CALLBACKGLUEPTRPTR(classname , func, type,type2) static void CB_ ## func(void *obj,type *name,type2 *name2) {static_cast< classname *>(obj)->func(name,name2);}
#define CALLBACKGLUEPTRPTRPTR(classname , func, type,type2,type3) static void CB_ ## func(void *obj,type *name,type2 *name2,type3 *name3) {static_cast< classname *>(obj)->func(name,name2,name3);}
#define CALLBACKGLUEVAL(classname , func, type) static void CB_ ## func(void *obj,type val) {static_cast< classname *>(obj)->func(val);}
0 голосов
/ 30 сентября 2008

Несколько неясно, чего вы пытаетесь достичь здесь. ясно, что указатели на функции - это не тот путь.

Может быть, вы ищете указатель на метод.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...