2009-03-14 51 views
7

Tôi có nhu cầu có khả năng thực hiện các cuộc gọi lại lớp siêu được định nghĩa bởi một lớp kế thừa từ nó. Tôi tương đối mới với C++ và từ những gì tôi có thể nói nó trông giống như chủ đề của thành viên-hàm-con trỏ là một khu vực rất u ám.C++ Con trỏ tới các hàm thành viên Thừa kế

Tôi đã thấy câu trả lời cho các câu hỏi và bài đăng trên blog ngẫu nhiên thảo luận về tất cả mọi thứ, nhưng tôi không chắc chắn liệu có bất kỳ câu hỏi nào trong số họ giải quyết cụ thể câu hỏi của tôi ở đây hay không.

Đây là một đoạn mã đơn giản minh họa những gì tôi đang cố gắng làm. Ví dụ này có thể không có nhiều ý nghĩa, nhưng nó giống với mã tôi đang cố gắng viết.

class A { 
    protected: 
    void doSomething(void (A::*someCallback)(int a)) { 
     (*this.*someCallback)(1234); 
    } 
}; 

class B : public A { 
    public: 
    void runDoIt() { doSomething(&B::doIt); } 
    void runDoSomethingElse() { doSomething(&B::doSomethingElse); } 
    protected: 
    void doIt(int foo) { 
     cout << "Do It! [" << foo << "]\n"; 
    } 
    void doSomethingElse(int foo) { 
     cout << "Do Something Else! [" << foo << "]\n"; 
    } 
}; 

int main(int argc, char *argv[]) { 
    B b; 
    b.runDoIt(); 
    b.runDoSomethingElse(); 
} 

Trả lời

7

Nếu bạn có thể sử dụng thư viện tăng cường, tôi khuyên bạn nên sử dụng hàm boost :: cho nhiệm vụ trong tầm tay.

class A { 
public: 
    void doSomething(boost::function< void (int) > callback) 
    { 
     callback(5); 
    } 
}; 

Sau đó, bất kỳ kế thừa (hoặc lớp bên ngoài) có thể sử dụng tăng :: ràng buộc làm thực hiện cuộc gọi:

class B { 
public: 
    void my_method(int a); 
}; 
void test() 
{ 
    B b; 
    A a; 
    a.doSomething(boost::bind(&B::my_method, &b, _1)); 
}; 

tôi đã không kiểm tra cú pháp chính xác và tôi đã gõ nó từ phía trên cùng của tôi đầu, nhưng đó là ít nhất là gần với mã thích hợp.

+0

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

+0

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! –

0

Bạn đang cố gọi phương thức được xác định trong lớp dẫn xuất là lớp cơ sở phương thức.
Nhưng chức năng này không được định nghĩa trong lớp cơ sở.

2

C++ cung cấp cấu trúc tốt hơn so với con trỏ hàm thành viên cho mục đích của bạn. (Nói chung, bạn không cần phải sử dụng con trỏ hàm trong C++ thuần túy được viết đúng cách). Hai tùy chọn trên đỉnh đầu của tôi:

1) Tạo một hàm ảo thuần túy trên A được gọi là doIt(). Gọi doIt() từ doSomething(). Trên B, giữ một biến thành viên enum để chỉ ra những gì bạn muốn làm, và thiết lập nó trước khi bạn gọi doSomething(). Ghi đè doIt() trong B, với công cụ chuyển đổi thực hiện/vỏ thành viên enum thành mã cơ bản mà bạn muốn chạy.

2) Tạo lớp DoInterface bằng một phương thức ảo thuần túy doIt() duy nhất. Tạo các dẫn xuất này cho lớp B của bạn có con trỏ tới cá thể B sở hữu và thực hiện doIt() gọi hàm thích hợp trên B trong mỗi triển khai DoInterface của bạn. doSomething() sẽ lấy một cá thể của một DoInterface làm tham số. Đây được gọi là mẫu đối tượng gọi lại.

+0

Tùy chọn 1, chức năng thành viên ảo, hầu như luôn là cách dễ nhất và thanh lịch nhất để thực hiện điều này, với một cảnh báo: các thành viên ảo không thể được gọi từ các nhà xây dựng lớp cơ sở hoặc trình phá hủy. Nhưng miễn là bạn không cần phải làm điều đó, đó là con đường để đi. –

+0

Nếu tôi chỉ phải lo lắng về một phương pháp, hoặc biết chính xác có bao nhiêu phương pháp tôi muốn "làm", điều này sẽ làm việc. Tôi sẽ xem xét nó chặt chẽ hơn nhưng tôi không nghĩ rằng điều này sẽ làm việc cho các mục đích của tôi như tôi đã khá nhiều làm điều này. –

+0

Đó là điểm của B là một lớp phái sinh của A. B biết chính xác có bao nhiêu phương pháp nó có thể được "thực hiện" và biết khi nào nên thực hiện, nó chỉ cần kích hoạt từ A, là điểm của ghi đè doIt() trên B. –

8

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ù ...

+0

Tôi sẽ cố gắng thực hiện theo cách này. Nó sẽ tiết tú hơn tôi mong đợi, nhưng tôi nghĩ nó sẽ tốt hơn giải pháp tốt nhất tiếp theo có liên quan đến vỏ và chuyển đổi. –

+0

Câu trả lời rất hay cho việc học. Cách tiếp cận bạn đưa ra gần với phiên bản giới hạn của libs tăng cường. Gọi lại là một phiên bản giới hạn của hàm boost :: và GenericCallback là một phiên bản ràng buộc bị rút gọn. –

+0

Một điều nữa là bạn có thể loại bỏ một số con trỏ và dịch chúng thành các tham chiếu.doSomething (Callback & c), hoặc GenericCallback có thể lưu trữ một & thay vì một con trỏ, làm cho nó rõ ràng hơn rằng nó sẽ không đối phó với bộ nhớ đó –

Các vấn đề liên quan