Динамически создать указатель на функцию, которая вызывает метод для данного экземпляра. - PullRequest
5 голосов
/ 02 апреля 2012

Я подозреваю, что это невозможно, но подумал, что спросить. Скажем, у меня есть класс с методом:

class A {
public:
    void b(int c);
};

Я могу сделать указатель на эту функцию-член:

void (A::*ptr)(int) = &A::b;
(someAInstance.*ptr)(123);

Я также могу злоупотреблять указателями на функции и создавать указатель, который напрямую принимает аргумент A (я не знаю, безопасно ли это, но он работает на моей машине):

void (*ptr2)(A*, int) = (void (*)(A*, int))&A::b;
(*ptr2)(&someAInstance, 123);

Что я хочу , так это как-то карри аргумент A и создание указателя на функцию, которая просто принимает int, но вызывает метод A::b для определенного A экземпляра I предопределено Экземпляр A будет оставаться постоянным для этого конкретного указателя функции, но может быть несколько указателей на функции, все из которых указывают на один и тот же метод A::b, но используют разные экземпляры A. Например, я мог бы сделать отдельную функцию-обертку:

A* someConstantA = new A;
void wrapper(int c) {
    someConstantA->b(c);
}

void (*ptr3)(int) = &wrapper;

Теперь я могу использовать ptr3, не зная, какой именно A отправляет вызов, но мне пришлось определить специальную функцию для его обработки. Мне нужен способ создания указателей для любого числа A экземпляров, поэтому я не могу жестко закодировать его таким образом. Возможно ли это каким-либо образом?


Редактировать : Должен был упомянуть, что я в ловушке на земле C ++ 03, и также не могу использовать Boost

Ответы [ 6 ]

3 голосов
/ 03 апреля 2012

Не создавайте оболочку функция , создайте оболочку функтор . Это позволяет вам инкапсулировать любое состояние, которое вы хотите (например, A*), в вызываемый объект.

class A {
public:
    void b(int c) {}
};

struct wrapper {
  A* pA;
  void (A::*pF)(int);
  void operator()(int c) { (pA->*pF)(c); }
  wrapper(A* pA, void(A::*pF)(int)) : pA(pA), pF(pF) {}
};

int main () {
  A a1;
  A a2;

  wrapper w1(&a1, &A::b);
  wrapper w2(&a2, &A::b);

  w1(3);
  w2(7);
}
1 голос
/ 03 апреля 2012

Если у вас достаточно новый компилятор (например, gcc 4.2+), он должен включать TR1, где вы можете использовать std::<i>tr1</i>::bind:

#include <cstdio>
#include <tr1/functional>

class A {
public:
    void b(int c) {
        printf("%p, %d\n", (void*)this, c);
    }
};

int main() {
    A* a = new A;

    std::tr1::function<void(int)> f =
        std::tr1::bind(&A::b, a, std::tr1::placeholders::_1);  // <--
    f(4);

    delete a;

    return 0;
}

Это также возможно в чистом C ++ 03 безTR1, но также гораздо более сложный:

std::binder1st<std::mem_fun1_t<void, A, int> > f =
    std::bind1st(std::mem_fun(&A::b), a);

Вы также можете написать свои собственные функциональные объекты.

Обратите внимание, что во всех вышеупомянутых случаях вы должны быть очень осторожны с временем жизни a, так как это пустой указатель.С std::tr1::bind вы можете по крайней мере обернуть указатель в std::tr1::shared_ptr, чтобы он мог жить столько же, сколько и объект функции.

std::tr1::shared_ptr<A> a (new A);
std::tr1::function<void(int)> f =
    std::tr1::bind(&A::b, a, std::tr1::placeholders::_1);
1 голос
/ 02 апреля 2012

Если вы используете C ++ 11, вы можете использовать лямбду (непроверенный код):

template<typename T, typename A>
 std::function<void(A)> curry(T& object, void (T::*ptr)(A))
{
  return [](A a) { (object.*ptr)(std::forward<A>(a)); }
}
0 голосов
/ 03 апреля 2012

Я что-то разобрался с шаблоном класса Delegate.

// T is class, R is type of return value, P is type of function parameter
template <class T, class R, class P> class Delegate
{
    typedef R (T::*DelegateFn)(P);
private:
    DelegateFn func;
public:
    Delegate(DelegateFn func)
    {
        this->func = func;
    }
    R Invoke(T * object, P v)
    {
        return ((object)->*(func))(v);
    }
};

class A {
private:
    int factor;
public:
    A(int f) { factor = f; }
    int B(int v) { return v * factor; }
};

int _tmain(int argc, _TCHAR* argv[])
{
    A * a1 = new A(2);
    A * a2 = new A(3);

    Delegate<A, int, int> mydelegate(&A::B);

    // Invoke a1->B
    printf("Result: %d\n", mydelegate.Invoke(a1, 555));

    // Invoke a2->B
    printf("Result: %d\n", mydelegate.Invoke(a2, 555));

    _getch();
    delete a1;
    delete a2;
    return 0;
}
0 голосов
/ 03 апреля 2012

То, что вы хотите сделать, невозможно.Чтобы понять почему, предположим, что это возможно - указатель функции должен указывать на функцию где-то в вашем исполняемом файле или в одной из его библиотек, поэтому он должен указывать на функцию, которая знает, какой экземпляр A вызвать, так же, как ваша оболочкафункция.Поскольку экземпляр A неизвестен до времени выполнения, вам придется создавать эти функции во время выполнения, что невозможно.

То, что вы пытаетесь сделать, возможно в C ++ 03до тех пор, пока вы будете рады передавать функциональный объект, а не указатель на функцию.

Поскольку другие уже дали решения с лямбдами C ++ 11, TR1 и boost (все из которых более красивы, чемниже), но вы упомянули, что вы не можете использовать C ++ 11, я добавлю один на чистом C ++ 03:

int main()
{
    void (A::*ptr)(int) = &A::b;
    A someAInstance;

    std::binder1st<std::mem_fun1_t<void,A,int> > fnObj =
         std::bind1st(std::mem_fun(ptr), &someAInstance);
    fnObj(321);
};
0 голосов
/ 03 апреля 2012

Я бы использовал для этого Boost :: bind .

В основном:

class A
{
    int myMethod(int x)
    {
        return x*x;
    }
};

int main(int argc, char* argv[])
{
    A test();
    auto callable = boost::bind(&A::myMethod, &A, _1);

    // These two lines are equivalent:
    cout << "object with 5 is: " << test.myMethod(5) << endl;
    cout << "callable with 5 is: " << callable(5) << endl;
    return 0;
}

Я думаю, это должно сработать. Я также использую здесь auto для вывода типа, возвращаемого boost :: bind () во время компиляции, который ваш компилятор может поддерживать или не поддерживать. См. Этот другой вопрос в stackoverflow для объяснения возвращаемого типа привязки.

Boost поддерживает обратно в Visual Studio 2003 (я думаю), и все это будет работать там, хотя я думаю, что вы будете использовать BOOST_AUTO. См. Другой вопрос, уже связанный для объяснения.

...