Sự cố là chức năng thành viên của B
không phải là chức năng thành viên của A
, ngay cả khi B
có nguồn gốc từ A
. Nếu bạn có một void (A::*)()
, bạn có thể gọi nó trên bất kỳ A *
nào, bất kể loại xuất phát thực tế của đối tượng được trỏ tới.
(Nguyên tắc tương tự sẽ áp dụng đối với một A &
quá, tất nhiên.)
Giả B
xuất phát từ A
, và C
xuất phát từ A
; là nó đã có thể xem xét một void (B::*)()
(nói) như là một void (A::*)()
, người ta sẽ có thể làm một cái gì đó như thế này:
A *c=new C;
A *b=new B;
void (A::*f)()=&B::fn;//fn is not defined in A
(c->*f)();
Và chức năng thành viên của B
sẽ được gọi vào một đối tượng kiểu C
. Kết quả sẽ không thể đoán trước ở mức tốt nhất.
Dựa trên mã ví dụ, và giả định không sử dụng một cái gì đó như thúc đẩy, tôi muốn được xu hướng cấu trúc gọi lại như một đối tượng:
class Callback {
public:
virtual ~Callback() {
}
virtual Do(int a)=0;
};
Sau đó, chức năng mà các cuộc gọi gọi lại mất một trong những các đối tượng này bằng cách nào đó chứ không phải là con trỏ hàm đơn giản:
class A {
protected:
void doSomething(Callback *c) {
c->Do(1234);
}
};
Sau đó bạn có thể có một cuộc gọi lại cho mỗi hàm xuất phát mà bạn muốn gọi. Đối với DoIT, ví dụ:
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_;
};
};
Một cách rõ ràng về cắt giảm trên soạn sẵn sẽ được đưa con trỏ hàm thành viên vào gọi lại, kể từ khi gọi lại được thừa kế là miễn phí để đối phó với các đối tượng của một loại hình cụ thể. Điều này sẽ làm cho các cuộc gọi lại một chút chung chung hơn, trong khi gọi trở lại nó sẽ gọi một hàm thành viên tùy ý trên một đối tượng kiểu 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);
};
này sẽ làm cho DoIT như thế này:
void B::runDoIt() {
BCallback cb(this,&B::doIt);
this->doSomething(&cb);
}
Đây có khả năng có thể "cải thiện", mặc dù không phải tất cả các độc giả có thể thấy nó khá theo cách đó, bởi templating nó:
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);
};
Sử dụng này, hàm runDoIt trên có thể trở thành:
void B::runDoIt() {
GenericCallback<B> cb(this,&B::doIt);
this->doSomething(&cb);
}
(Việc gọi lại chung cũng có thể được tạo khuôn trên con trỏ hàm thành viên, mặc dù điều này không có lợi thế thực tế trong hầu hết các trường hợp. Nó chỉ cần gõ nhiều hơn.)
Tôi đã tìm thấy cấu trúc mọi thứ theo cách này để trở nên tốt, vì nó không yêu cầu thừa kế. Do đó nó áp đặt một vài hạn chế đối với mã được gọi lại, đó là tâm trí của tôi luôn luôn là một điều tốt, và tôi đã thấy rằng để vượt quá độ dài mà khó loại bỏ hoàn toàn. Không thể dựa trên ví dụ để cho biết liệu phương pháp này có thực sự phù hợp hay không, mặc dù ...
Tôi sẽ đứng thứ hai cách tiếp cận này. Sử dụng boost ở đây tạo ra rất nhiều ý nghĩa, họ đã dành rất nhiều nỗ lực vào loại điều này. – sstock
Tôi hơi do dự khi sử dụng bất kỳ thứ gì, nhưng tôi sẽ thử. Nó hoạt động nhiều hơn hoặc ít hơn như bạn đã mô tả ở đây! –