El problema es que una función miembro de B
no es una función miembro de A
, incluso si B
deriva de A
. Si tiene un void (A::*)()
, puede invocarlo en cualquier A *
, independientemente del tipo de objeto real al que se apunta.
(El mismo principio se aplicaría a una A &
también, por supuesto.)
Supongamos B
deriva de A
, y C
deriva de A
; estaban fuera posible considerar un void (B::*)()
(por ejemplo) como void (A::*)()
, uno sería capaz de hacer algo como esto:
A *c=new C;
A *b=new B;
void (A::*f)()=&B::fn;//fn is not defined in A
(c->*f)();
Y la función de miembro de B
se pidió a un objeto de tipo C
. Los resultados serían impredecibles en el mejor de los casos.
Basado en el código de ejemplo, y suponiendo que no uso de algo así como impulso, estaría inclinado a estructurar la devolución de llamada como un objeto:
class Callback {
public:
virtual ~Callback() {
}
virtual Do(int a)=0;
};
A continuación, la función que llama a la devolución de llamada tiene una de estos objetos de alguna manera, más que una función de puntero sin formato:
class A {
protected:
void doSomething(Callback *c) {
c->Do(1234);
}
};
a continuación, podría tener una devolución de llamada por función derivada usted está interesado en llamar. Para DOIT, por ejemplo:
class B:public A {
public:
void runDoIt() {
DoItCallback cb(this);
this->doSomething(&cb);
}
protected:
void doIt(int foo) {
// whatever
}
private:
class DoItCallback:public Callback {
public:
DoItCallback(B *b):b_(b) {}
void Do(int a) {
b_->doIt(a);
}
private:
B *b_;
};
};
Una manera obvia de recortar sobre la plancha de caldera sería poner el puntero de función miembro en la devolución de llamada, ya que la devolución de llamada derivada es libre para hacer frente a objetos de un tipo específico. Esto haría que la devolución de llamada un poco más genérico, en el que cuando llamó sería invocar una función miembro arbitrario en un objeto de tipo B:
class BCallback:public Callback {
public:
BCallback(B *obj,void (B::*fn)(int)):obj_(obj),fn_(fn) {}
void Do(int a) {
(obj_->*fn_)(a);
}
private:
B *obj_;
void (B::*fn_)(int);
};
Esto haría doIT así:
void B::runDoIt() {
BCallback cb(this,&B::doIt);
this->doSomething(&cb);
}
potencialmente, esto podría ser "mejorado", aunque no todos los lectores pueden ver bastante de esa manera, por lo templating:
template<class T>
class GenericCallback:public Callback {
public:
GenericCallback(T *obj,void (T::*fn)(int)):obj_(obj),fn_(fn) {}
void Do(int a) {
(obj_->*fn_)(a);
}
private:
T *obj_;
void (T::*fn_)(int);
};
uso de este, la función runDoIt anterior podría llegar a ser:
void B::runDoIt() {
GenericCallback<B> cb(this,&B::doIt);
this->doSomething(&cb);
}
(La devolución de llamada genérica podría también ser templated en el propio puntero de función miembro, aunque esto es poco probable que proporcione ninguna ventaja práctica en la mayoría de los casos. Es simplemente más tipeo.)
He encontrado que las cosas de estructuración de esta manera resultan bien, ya que no requieren herencia. Por lo tanto, impone pocas restricciones sobre el código que se devolverá, lo que en mi opinión siempre es algo bueno, y he descubierto que supera la verbosidad que es difícil de eliminar por completo. Imposible basado en el ejemplo para decir si este enfoque realmente se adaptaría, aunque ...
Yo estaría de acuerdo con este enfoque. Usar el impulso aquí tiene mucho sentido, han dedicado un gran esfuerzo en este tipo de cosas. – sstock
Estaba un poco indeciso de usar boost para cualquier cosa, pero voy a intentarlo. ¡Funciona más o menos como lo describió aquí! –