Puntero de función a función miembro

89

Me gustaría configurar un puntero de función como miembro de una clase que es un puntero a otra función en la misma clase. Las razones por las que hago esto son complicadas.

En este ejemplo, me gustaría que la salida fuera "1"

class A {
public:
 int f();
 int (*x)();
}

int A::f() {
 return 1;
}


int main() {
 A a;
 a.x = a.f;
 printf("%d\n",a.x())
}

Pero esto falla en la compilación. ¿Por qué?

Miguel
fuente
@jww y verifique la respuesta de CiroSantilli en esa pregunta, otras respuestas están más o menos fuera de tema. Básicamente, solo int (C :: * function_pointer_var) (int) = & C :: method; luego C c; y (c. * function_pointer_var) (2).
jw_

Respuestas:

157

La sintaxis es incorrecta. Un puntero de miembro es una categoría de tipo diferente de un puntero ordinario. El puntero de miembro deberá usarse junto con un objeto de su clase:

class A {
public:
 int f();
 int (A::*x)(); // <- declare by saying what class it is a pointer to
};

int A::f() {
 return 1;
}


int main() {
 A a;
 a.x = &A::f; // use the :: syntax
 printf("%d\n",(a.*(a.x))()); // use together with an object of its class
}

a.xtodavía no dice en qué objeto se llamará la función. Simplemente dice que desea usar el puntero almacenado en el objeto a. Anteponer aotro tiempo como operando izquierdo al .*operador le dirá al compilador en qué objeto llamar a la función.

Johannes Schaub - litb
fuente
Sé que esto es antiguo, pero no entiendo el uso de. (a.*a.x)()¿Por qué (a.*x)()no funciona?
Gaurav Sehgal
3
@gau porque x no está en el alcance
Johannes Schaub - litb
13
También tengo que buscar esto cada vez que lo uso. La sintaxis es confusa, pero tiene sentido si la desglosa. a.xes un puntero a una función miembro de la clase A. *a.xelimina la referencia del puntero, por lo que ahora es una referencia de función. a.(*a.x)"enlaza" la función a una instancia (como a.f). (a.(*a.x))Es necesario agrupar esta sintaxis compleja y, de (a.(*a.x))()hecho, invoca el método asin argumentos.
jwm
23

int (*x)()no es un puntero a la función miembro. Un puntero a la función miembro se escribe así: int (A::*x)(void) = &A::f;.

Bertrand Marron
fuente
17

Llamar a la función miembro en el comando de cadena

#include <iostream>
#include <string>


class A 
{
public: 
    void call();
private:
    void printH();
    void command(std::string a, std::string b, void (A::*func)());
};

void A::printH()
{
    std::cout<< "H\n";
}

void A::call()
{
    command("a","a", &A::printH);
}

void A::command(std::string a, std::string b, void (A::*func)())
{
    if(a == b)
    {
        (this->*func)();
    }
}

int main()
{
    A a;
    a.call();
    return 0;
}

Preste atención a (this->*func)();la forma de declarar el puntero de función con el nombre de la clasevoid (A::*func)()

Él para
fuente
11

Necesita usar un puntero a una función miembro, no solo un puntero a una función.

class A { 
    int f() { return 1; }
public:
    int (A::*x)();

    A() : x(&A::f) {}
};

int main() { 
   A a;
   std::cout << (a.*a.x)();
   return 0;
}
Jerry Coffin
fuente
3

Si bien esto se basa en las excelentes respuestas en otras partes de esta página, tuve un caso de uso que no fue completamente resuelto por ellos; para un vector de punteros a funciones, haga lo siguiente:

#include <iostream>
#include <vector>
#include <stdio.h>
#include <stdlib.h>

class A{
public:
  typedef vector<int> (A::*AFunc)(int I1,int I2);
  vector<AFunc> FuncList;
  inline int Subtract(int I1,int I2){return I1-I2;};
  inline int Add(int I1,int I2){return I1+I2;};
  ...
  void Populate();
  void ExecuteAll();
};

void A::Populate(){
    FuncList.push_back(&A::Subtract);
    FuncList.push_back(&A::Add);
    ...
}

void A::ExecuteAll(){
  int In1=1,In2=2,Out=0;
  for(size_t FuncId=0;FuncId<FuncList.size();FuncId++){
    Out=(this->*FuncList[FuncId])(In1,In2);
    printf("Function %ld output %d\n",FuncId,Out);
  }
}

int main(){
  A Demo;
  Demo.Populate();
  Demo.ExecuteAll();
  return 0;
}

Algo como esto es útil si está escribiendo un intérprete de comandos con funciones indexadas que deben combinarse con sintaxis de parámetros y consejos de ayuda, etc. Posiblemente también sea útil en menús.

Búho
fuente
1
Como se define, AFunc es un puntero a la función miembro que toma dos entradas y devuelve un vector de entradas. Pero los miembros apuntaron a regresar int, ¿verdad? Creo que la declaración typedef debería ser typedef int (A::*AFunc)(int I1,int I2);
riderBill
2

Si bien desafortunadamente no puede convertir un puntero de función miembro existente en un puntero de función simple, puede crear una plantilla de función de adaptador de una manera bastante sencilla que envuelva un puntero de función miembro conocido en tiempo de compilación en una función normal como esta:

template <class Type>
struct member_function;

template <class Type, class Ret, class... Args>
struct member_function<Ret(Type::*)(Args...)>
{
    template <Ret(Type::*Func)(Args...)>
    static Ret adapter(Type &obj, Args&&... args)
    {
        return (obj.*Func)(std::forward<Args>(args)...);
    }
};

template <class Type, class Ret, class... Args>
struct member_function<Ret(Type::*)(Args...) const>
{
    template <Ret(Type::*Func)(Args...) const>
    static Ret adapter(const Type &obj, Args&&... args)
    {
        return (obj.*Func)(std::forward<Args>(args)...);
    }
};

 

int (*func)(A&) = &member_function<decltype(&A::f)>::adapter<&A::f>;

Tenga en cuenta que para llamar a la función miembro, se Adebe proporcionar una instancia de .

IllidanS4 apoya a Monica
fuente
Me has inspirado, @ IllidanS4. Mira mi respuesta. +1
memtha
1

Sobre la base de la respuesta de @ IllidanS4, he creado una clase de plantilla que permite que prácticamente cualquier función miembro con argumentos predefinidos e instancia de clase se pase por referencia para una llamada posterior.



template<class RET, class... RArgs> class Callback_t {
public:
    virtual RET call(RArgs&&... rargs) = 0;
    //virtual RET call() = 0;
};

template<class T, class RET, class... RArgs> class CallbackCalltimeArgs : public Callback_t<RET, RArgs...> {
public:
    T * owner;
    RET(T::*x)(RArgs...);
    RET call(RArgs&&... rargs) {
        return (*owner.*(x))(std::forward<RArgs>(rargs)...);
    };
    CallbackCalltimeArgs(T* t, RET(T::*x)(RArgs...)) : owner(t), x(x) {}
};

template<class T, class RET, class... Args> class CallbackCreattimeArgs : public Callback_t<RET> {
public:
    T* owner;
    RET(T::*x)(Args...);
    RET call() {
        return (*owner.*(x))(std::get<Args&&>(args)...);
    };
    std::tuple<Args&&...> args;
    CallbackCreattimeArgs(T* t, RET(T::*x)(Args...), Args&&... args) : owner(t), x(x),
        args(std::tuple<Args&&...>(std::forward<Args>(args)...)) {}
};

Prueba / ejemplo:

class container {
public:
    static void printFrom(container* c) { c->print(); };
    container(int data) : data(data) {};
    ~container() {};
    void print() { printf("%d\n", data); };
    void printTo(FILE* f) { fprintf(f, "%d\n", data); };
    void printWith(int arg) { printf("%d:%d\n", data, arg); };
private:
    int data;
};

int main() {
    container c1(1), c2(20);
    CallbackCreattimeArgs<container, void> f1(&c1, &container::print);
    Callback_t<void>* fp1 = &f1;
    fp1->call();//1
    CallbackCreattimeArgs<container, void, FILE*> f2(&c2, &container::printTo, stdout);
    Callback_t<void>* fp2 = &f2;
    fp2->call();//20
    CallbackCalltimeArgs<container, void, int> f3(&c2, &container::printWith);
    Callback_t<void, int>* fp3 = &f3;
    fp3->call(15);//20:15
}

Obviamente, esto solo funcionará si los argumentos dados y la clase propietaria siguen siendo válidos. En cuanto a la legibilidad ... perdóneme.

Editar: eliminó el malloc innecesario haciendo que la tupla sea un almacenamiento normal. Se agregó un tipo heredado para la referencia. Se agregó la opción para proporcionar todos los argumentos en el momento de la llamada. Ahora trabajando para tener ambos ...

Edición 2: Como prometí, ambos. La única restricción (que veo) es que los argumentos predefinidos deben venir antes que los argumentos proporcionados en tiempo de ejecución en la función de devolución de llamada. Gracias a @Chipster por su ayuda con el cumplimiento de gcc. Esto funciona en gcc en ubuntu y visual studio en windows.

#ifdef _WIN32
#define wintypename typename
#else
#define wintypename
#endif

template<class RET, class... RArgs> class Callback_t {
public:
    virtual RET call(RArgs... rargs) = 0;
    virtual ~Callback_t() = default;
};

template<class RET, class... RArgs> class CallbackFactory {
private:
    template<class T, class... CArgs> class Callback : public Callback_t<RET, RArgs...> {
    private:
        T * owner;
        RET(T::*x)(CArgs..., RArgs...);
        std::tuple<CArgs...> cargs;
        RET call(RArgs... rargs) {
            return (*owner.*(x))(std::get<CArgs>(cargs)..., rargs...);
        };
    public:
        Callback(T* t, RET(T::*x)(CArgs..., RArgs...), CArgs... pda);
        ~Callback() {};
    };
public:
    template<class U, class... CArgs> static Callback_t<RET, RArgs...>* make(U* owner, CArgs... cargs, RET(U::*func)(CArgs..., RArgs...));
};
template<class RET2, class... RArgs2> template<class T2, class... CArgs2> CallbackFactory<RET2, RArgs2...>::Callback<T2, CArgs2...>::Callback(T2* t, RET2(T2::*x)(CArgs2..., RArgs2...), CArgs2... pda) : x(x), owner(t), cargs(std::forward<CArgs2>(pda)...) {}
template<class RET, class... RArgs> template<class U, class... CArgs> Callback_t<RET, RArgs...>* CallbackFactory<RET, RArgs...>::make(U* owner, CArgs... cargs, RET(U::*func)(CArgs..., RArgs...)) {
    return new wintypename CallbackFactory<RET, RArgs...>::Callback<U, CArgs...>(owner, func, std::forward<CArgs>(cargs)...);
}
memtha
fuente