poo - Llamando a los métodos de clase C++ a través de un puntero de función
punteros c++ (10)
¿Cómo obtengo un puntero de función para una función de miembro de 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 de miembro, agregue el nombre de clase en la declaración de tipo:
typedef void(Dog::*BarkFunction)(void);
Luego, para invocar el método, usa el operador ->*
:
(pDog->*pBark)();
Además, si es posible, me gustaría invocar el constructor a través de un puntero también. ¿Es esto posible? 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 utilizar un método de fábrica, que es básicamente una función estática que llama al constructor por ti. Vea el siguiente código para un ejemplo.
He modificado tu código para hacer básicamente lo que describes. 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 puede utilizar un Dog*
en lugar de un Animal*
gracias a la magia del polimorfismo, el tipo de puntero a una función no sigue las reglas de búsqueda de la jerarquía de clases. Por lo tanto, un puntero de método Animal no es compatible con un puntero de método de Perro, en otras palabras, no puede asignar un Dog* (*)()
a una variable de tipo Animal* (*)()
.
El método newDog
estático es un ejemplo simple de una fábrica, que simplemente crea y devuelve nuevas instancias. Al ser una función estática, tiene un typedef
regular (sin calificador de clase).
Habiendo respondido lo anterior, me pregunto si no hay una mejor manera de lograr lo que necesita. Hay algunos escenarios específicos en los que haría este tipo de cosas, pero puede encontrar que hay otros patrones que funcionan mejor para su problema. Si describes en términos más generales lo que intentas lograr, ¡la mente colmena puede resultar aún más útil!
Relacionado con lo anterior, sin duda encontrará la biblioteca Boost bind y otros módulos relacionados muy útiles.
¿Cómo obtengo un puntero de función para una función de miembro de 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, me gustaría invocar el constructor a través de un puntero también:
NewAnimalFunction pNew = &Dog::Dog;
Animal* pAnimal = (*pNew)();
¿Es esto posible? De ser así, ¿cuál es la forma preferida de hacerlo?
La razón por la que no puede usar los punteros de función para llamar a funciones de miembro es que los punteros de función ordinarios generalmente son solo la dirección de memoria de la función.
Para llamar a una función de miembro, necesita saber dos cosas:
- Qué función miembro llamar
- Qué instancia se debe usar (cuya función de miembro)
Los punteros de función ordinarios no pueden almacenar ambos. Los punteros de función de miembro de C ++ se utilizan para almacenar a), por lo que debe especificar la instancia explícitamente al llamar a un puntero de función miembro.
Lea this para más detalles:
// 1 define un puntero a la función e inicializa a 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'');
No creo que nadie haya explicado aquí que un problema es que necesita " punteros de miembro " en lugar de punteros de función normales.
Los indicadores de miembros para las funciones no son simplemente indicadores de función. En términos de implementación, el compilador no puede usar una dirección de función simple porque, en general, no conoce la dirección a llamar hasta que sepa a qué objeto desreferencia (funciones virtuales de pensamiento). También necesita conocer el objeto para proporcionar this
parámetro implícito, por supuesto.
Habiendo dicho que los necesitas, ahora diré que realmente necesitas evitarlos. En serio, los indicadores de miembros son un dolor. Es mucho más sensato mirar patrones de diseño orientados a objetos que logren el mismo objetivo, o usar una boost::function
o lo que sea como se mencionó anteriormente, suponiendo que tome esa decisión, eso es.
Si está suministrando el puntero de función al código existente, por lo que 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. Hubo una vez una expresión no tan inusual en esta línea para trabajar con el antiguo código C que necesita indicadores de función
class myclass
{
public:
virtual void myrealmethod () = 0;
static void myfunction (myclass *p);
}
void myclass::myfunction (myclass *p)
{
p->myrealmethod ();
}
Como myfunction
es realmente solo una función normal (cuestiones de alcance a un lado), se puede encontrar un puntero a la función en la forma C normal.
EDITAR : este tipo de método se denomina "método de clase" o "función de miembro estático". La diferencia principal de una función que no es miembro es que, si hace referencia desde fuera de la clase, debe especificar el alcance utilizando el operador de resolución ::
scope. Por ejemplo, para obtener el puntero a la función, use &myclass::myfunction
y para llamarlo use myclass::myfunction (arg);
.
Este tipo de cosas es bastante común cuando se utilizan las antiguas API de Win32, que originalmente se diseñaron para C en lugar de C ++. Por supuesto, en ese caso, el parámetro es normalmente LPARAM o similar en lugar de un puntero, y se necesita algo de fundición.
Para crear un nuevo objeto, puede usar la ubicación nueva, como se mencionó anteriormente, o hacer que su clase implemente un método clone () que cree una copia del objeto. A continuación, puede llamar a este método de clonación utilizando un puntero de función miembro como se explicó anteriormente para crear nuevas instancias del objeto. La ventaja de clon es que a veces puede estar trabajando con un puntero a una clase base donde no se conoce el tipo de objeto. En este caso, un método clone () puede ser más fácil de usar. Además, clone () te permitirá copiar el estado del objeto si eso es lo que quieres.
Todavía no entiendo ''por qué'' si quieres llamar a una función miembro de objetos y luego simplemente pasas un puntero al objeto? Si la gente se queja porque le permite encapsular mejor la clase, ¿por qué no hacer una clase de interfaz de la que heredan todas las clases?
Un puntero a la función de un miembro de la clase es un problema que es realmente adecuado para usar boost :: function. Pequeño ejemplo:
#include <boost/function.hpp>
#include <iostream>
class Dog
{
public:
Dog (int i) : tmp(i) {}
void bark ()
{
std::cout << "woof: " << tmp << std::endl;
}
private:
int tmp;
};
int main()
{
Dog* pDog1 = new Dog (1);
Dog* pDog2 = new Dog (2);
//BarkFunction pBark = &Dog::bark;
boost::function<void (Dog*)> f1 = &Dog::bark;
f1(pDog1);
f1(pDog2);
}
Vine aquí para aprender cómo crear un puntero de función (no un puntero de método) a partir de un método, pero ninguna de las respuestas aquí proporciona una solución. Así que lo pensé y encontré una buena solución que creo que vale la pena compartir:
template <class T> struct MethodHelper;
template <class C, class Ret, class... Args> struct MethodHelper<Ret(C::*)(Args...)> {
using T = Ret (C::*)(Args...);
template <T m> static Ret call(C* object, Args... args) {
return (object->*m)(args...);
}
};
#define METHOD_FP(m) MethodHelper<decltype(m)>::call<m>
Entonces, para su ejemplo, ahora lo haría:
Dog dog;
using BarkFunction = void (*)(Dog*);
BarkFunction bark = METHOD_FP(&Dog::bark);
(*bark)(&dog); // or simply bark(&dog)
Ejemplo ejecutable mínimo
#include <cassert>
class C {
public:
int i;
C(int i) : i(i) {}
int m(int j) { return this->i + j; }
};
int main() {
// Get a method pointer.
int (C::*p)(int) = &C::m;
// Create a test object.
C c(1);
C *cp = &c;
// Operator .*
assert((c.*p)(2) == 3);
// Operator ->*
assert((cp->*p)(2) == 3);
}
No puede cambiar el orden del paréntesis u omitirlo. Lo siguiente no funciona:
c.*p(2)
c.*(p)(2)
Estándar C ++ 11
.*
y ->*
son operadores de singe introducidos en C ++ para este propósito, y no están presentes en C.
Proyecto estándar C ++ 11 N3337 :
- 2.13 "Operadores y puntuadores" tiene una lista de todos los operadores, que contiene
.*
Y->*
. - 5.5 "Operadores de puntero a miembro" explica lo que hacen
typedef void (Dog::*memfun)();
memfun doSomething = &Dog::bark;
....
(pDog->*doSomething)(); // if pDog is a pointer
// (pDog.*doSomething)(); // if pDog is a reference