Проблема в том, что функция-член B
не является функцией-членом A
, даже если B
происходит от A
. Если у вас есть void (A::*)()
, вы можете вызвать его на любом A *
, независимо от фактического производного типа объекта, на который указывает.
(Конечно, тот же принцип применим и к A &
.)
Предположим, B
происходит от A
, а C
происходит от A
; если бы можно было считать void (B::*)()
(скажем) void (A::*)()
, можно было бы сделать что-то вроде этого:
A *c=new C;
A *b=new B;
void (A::*f)()=&B::fn;//fn is not defined in A
(c->*f)();
И функция-член B
будет вызываться для объекта типа C
. Результаты будут в лучшем случае непредсказуемыми.
Исходя из примера кода и предполагая, что не используется что-то вроде boost, я склонен структурировать обратный вызов как объект:
class Callback {
public:
virtual ~Callback() {
}
virtual Do(int a)=0;
};
Тогда функция, вызывающая обратный вызов, каким-то образом получает один из этих объектов, а не простой указатель на функцию:
class A {
protected:
void doSomething(Callback *c) {
c->Do(1234);
}
};
Тогда вы могли бы иметь один обратный вызов для каждой производной функции, которую вы хотите вызвать. Например, для:
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_;
};
};
Очевидный способ урезать шаблон - поместить указатель на функцию-член в обратный вызов, поскольку производный обратный вызов может свободно работать с объектами определенного типа. Это сделало бы обратный вызов немного более общим, так как при обратном вызове он вызывал бы произвольную функцию-член для объекта типа 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);
};
Это сделало бы так:
void B::runDoIt() {
BCallback cb(this,&B::doIt);
this->doSomething(&cb);
}
Это потенциально может быть «улучшено», хотя не все читатели могут увидеть его таким образом, если его шаблонировать:
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);
};
Используя это, приведенная выше функция runDoIt может стать:
void B::runDoIt() {
GenericCallback<B> cb(this,&B::doIt);
this->doSomething(&cb);
}
(Общий обратный вызов также может быть создан на основе указателя на функцию-член, хотя в большинстве случаев это вряд ли обеспечит какое-либо практическое преимущество. Это просто больше ввода).
Я нашел способ структурирования таким образом, чтобы получилось хорошо, так как он не требует наследования. Поэтому он накладывает несколько ограничений на код, который должен вызываться обратно, что, на мой взгляд, всегда хорошо, и я обнаружил, что это перевешивает многословие, которое трудно полностью исключить. Невозможно, основываясь на примере, сказать, подойдет ли этот подход, хотя ...