Вызов указателя функции C ++ на конкретный экземпляр объекта
Вопрос
У меня есть указатель на функцию, определенный с помощью:
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 ++, которая будет выполнять такого рода перенос указателя на функцию-член в обычный указатель на функцию?
Решение
Я настоятельно рекомендую превосходную библиотеку FastDelegate Дона Клугстона.Он предоставляет все, что вы ожидаете от реального делегата, и в большинстве случаев компилируется до нескольких инструкций ASM.В сопроводительной статье также полезно ознакомиться с указателями на функции-члены.
Другие советы
Вы можете использовать указатели на функции для индексации в 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 */ }
};
Убегайте от необработанных указателей на функции C ++ и используйте std::function
вместо этого.
Вы можете использовать boost::function
если вы используете старый компилятор, такой как Visual Studio 2008, который не поддерживает C ++ 11.
boost:function
и std::function
это одно и то же - они добавили довольно много boost-материала в std-библиотеку для C ++ 11.
Примечание:возможно, вы захотите ознакомиться с документацией по функции boost, а не с документацией Microsoft, поскольку ее легче понять
Вы можете найти Часто задаваемые вопросы по C ++ от Маршалла Клайна полезно для того, чего вы пытаетесь достичь.
Читайте о указатели на элементы.Чтобы вызвать метод в производном классе, метод должен быть объявлен в базовом классе как виртуальный и переопределен в базовом классе, а ваш указатель должен указывать на метод базового класса.Подробнее о указатели на виртуальных участников.
Если вы взаимодействуете с библиотекой 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
}
К сожалению, тип 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.
На практике вы видите два решения этой проблемы:специальные системы, в которых вы передаете void * и событие, а также иерархии с виртуальными функциями в базовом классе, например оконные системы
Вы упомянули, что boost для вас не вариант, но доступен ли вам TR1?
TR1 предлагает объекты function , bind и mem_fn на основе библиотеки boost, и, возможно, она уже есть в комплекте с вашим компилятором.Это еще не стандартно, но по крайней мере в двух компиляторах, которыми я недавно пользовался, это было.
http://en.wikipedia.org/wiki/Technical_Report_1
http://msdn.microsoft.com/en-us/library/bb982702.aspx
Несколько неясно, чего вы пытаетесь здесь добиться.что ясно, так это то, что указатели на функции - это не выход.
возможно, то, что вы ищете, - это указатель на метод.
У меня есть набор классов именно для этой вещи, которые я использую в своем фреймворке 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);}