¿Cómo puedo crear un puntero a una función miembro y llamarlo?

Resuelto Tony the Pony asked hace 14 años • 11 respuestas

¿Cómo obtengo un puntero de función para una función miembro de una clase y luego llamo a esa función miembro con un objeto específico? Me gustaría escribir:

class Dog : Animal
{
    Dog ();
    void bark ();
}

…
Dog* pDog = new Dog ();
BarkFunction pBark = &Dog::bark;
(*pBark) (pDog);
…

Además, si es posible, también me gustaría invocar el constructor mediante un puntero:

NewAnimalFunction pNew = &Dog::Dog;
Animal* pAnimal = (*pNew)();    

¿Es esto posible y, de ser así, cuál es la forma preferida de hacerlo?

Tony the Pony avatar Sep 28 '09 15:09 Tony the Pony
Aceptado

Lea esto para más detalles:

// 1 define a function pointer and initialize to NULL

int (TMyClass::*pt2ConstMember)(float, char, char) const = NULL;

// C++

class TMyClass
{
public:
   int DoIt(float a, char b, char c){ cout << "TMyClass::DoIt"<< endl; return a+b+c;};
   int DoMore(float a, char b, char c) const
         { cout << "TMyClass::DoMore" << endl; return a-b+c; };

   /* more of TMyClass */
};
pt2ConstMember = &TMyClass::DoIt; // note: <pt2Member> may also legally point to &DoMore

// Calling Function using Function Pointer

(*this.*pt2ConstMember)(12, 'a', 'b');
Satbir avatar Sep 28 '2009 10:09 Satbir

¿Cómo obtengo un puntero de función para una función miembro de una clase y luego llamo a esa función miembro con un objeto específico?

Es más fácil comenzar con un typedef. Para una función miembro, agrega el nombre de clase en la declaración de tipo:

typedef void(Dog::*BarkFunction)(void);

Luego, para invocar el método, utiliza el ->*operador:

(pDog->*pBark)();

Además, si es posible, también me gustaría invocar al constructor mediante un puntero. ¿Es esto posible y, de ser así, cuál es la forma preferida de hacerlo?

No creo que puedas trabajar con constructores como este: los ctors y dtors son especiales. La forma normal de lograr ese tipo de cosas sería usar un método de fábrica, que es básicamente una función estática que llama al constructor por usted. Vea el código a continuación para ver un ejemplo.

He modificado tu código para hacer básicamente lo que describe. Hay algunas advertencias a continuación.

#include <iostream>

class Animal
{
public:

    typedef Animal*(*NewAnimalFunction)(void);

    virtual void makeNoise()
    {
        std::cout << "M00f!" << std::endl;
    }
};

class Dog : public Animal
{
public:

    typedef void(Dog::*BarkFunction)(void);

    typedef Dog*(*NewDogFunction)(void);

    Dog () {}

    static Dog* newDog()
    {
        return new Dog;
    }

    virtual void makeNoise ()
    {
        std::cout << "Woof!" << std::endl;
    }
};

int main(int argc, char* argv[])
{
    // Call member function via method pointer
    Dog* pDog = new Dog ();
    Dog::BarkFunction pBark = &Dog::makeNoise;

    (pDog->*pBark)();

    // Construct instance via factory method
    Dog::NewDogFunction pNew = &Dog::newDog;

    Animal* pAnimal = (*pNew)();

    pAnimal->makeNoise();

    return 0;
}

Ahora bien, aunque normalmente puedes usar a Dog*en lugar de an Animal*gracias a la magia del polimorfismo, el tipo de puntero de función no sigue las reglas de búsqueda de la jerarquía de clases. Entonces, un puntero de método Animal no es compatible con un puntero de método Perro; en otras palabras, no se puede asignar a Dog* (*)()una variable de tipo Animal* (*)().

El método estático newDoges un ejemplo sencillo de fábrica, que simplemente crea y devuelve nuevas instancias. Al ser una función estática, tiene un regular typedef(sin calificador de clase).

Habiendo respondido lo anterior, me pregunto si no existe una mejor manera de lograr lo que necesita. Hay algunos escenarios específicos en los que harías este tipo de cosas, pero es posible que descubras que hay otros patrones que funcionan mejor para tu problema. Si describe en términos más generales lo que está tratando de lograr, ¡la mente colmena puede resultar aún más útil!

En relación con lo anterior, sin duda encontrará muy útiles la biblioteca de enlace Boost y otros módulos relacionados.

gavinb avatar Sep 28 '2009 10:09 gavinb

No creo que nadie haya explicado aquí que un problema es que se necesitan " punteros de miembros " en lugar de punteros de funciones normales.

Los punteros de miembros a funciones no son simplemente punteros de funciones. En términos de implementación, el compilador no puede usar una dirección de función simple porque, en general, no se sabe la dirección a llamar hasta que se sabe qué objeto desreferenciar (piense en funciones virtuales). thisPor supuesto, también necesita conocer el objeto para poder proporcionar el parámetro implícito.

Habiendo dicho que los necesitas, ahora diré que realmente necesitas evitarlos. En serio, los consejos para miembros son una molestia. Es mucho más sensato observar patrones de diseño orientados a objetos que logran el mismo objetivo, o usar uno boost::functiono lo que sea como se mencionó anteriormente, suponiendo que pueda tomar esa decisión, claro.

Si está proporcionando ese puntero de función a un código existente, entonces realmente necesita un puntero de función simple, debe escribir una función como un miembro estático de la clase. Una función miembro estática no comprende this, por lo que deberá pasar el objeto como un parámetro explícito. Una vez hubo un modismo no tan inusual en este sentido para trabajar con código C antiguo que necesita punteros de función.

class myclass
{
  public:
    virtual void myrealmethod () = 0;

    static void myfunction (myclass *p);
}

void myclass::myfunction (myclass *p)
{
  p->myrealmethod ();
}

Dado que myfunctionen realidad es solo una función normal (aparte de los problemas de alcance), se puede encontrar un puntero de función en la forma normal de C.

EDITAR : este tipo de método se denomina "método de clase" o "función miembro estática". La principal diferencia con una función que no es miembro es que, si hace referencia a ella desde fuera de la clase, debe especificar el alcance mediante el ::operador de resolución de alcance. Por ejemplo, para obtener el puntero de función, use &myclass::myfunctiony para llamarlo use myclass::myfunction (arg);.

Este tipo de cosas es bastante común cuando se utilizan las antiguas API de Win32, que fueron diseñadas originalmente para C en lugar de C++. Por supuesto, en ese caso, el parámetro normalmente es LPARAM o similar en lugar de un puntero, y se necesita algo de conversión.

 avatar Sep 28 '2009 14:09
typedef void (Dog::*memfun)();
memfun doSomething = &Dog::bark;
....
(pDog->*doSomething)(); // if pDog is a pointer
// (pDog.*doSomething)(); // if pDog is a reference
Khaled Alshaya avatar Sep 28 '2009 08:09 Khaled Alshaya