2008-12-30 38 views
48

Tôi đang sử dụng API yêu cầu tôi chuyển một con trỏ hàm làm gọi lại. Tôi đang cố gắng sử dụng API này từ lớp của tôi nhưng tôi đang nhận được lỗi biên dịch.Làm cách nào để chuyển một hàm thành viên của lớp như một cuộc gọi lại?

Đây là những gì tôi đã làm từ constructor của tôi:

m_cRedundencyManager->Init(this->RedundencyManagerCallBack); 

này không biên dịch - tôi nhận được lỗi sau:

Error 8 error C3867: 'CLoggersInfra::RedundencyManagerCallBack': function call missing argument list; use '&CLoggersInfra::RedundencyManagerCallBack' to create a pointer to member

Tôi đã thử các gợi ý để sử dụng &CLoggersInfra::RedundencyManagerCallBack - không làm việc cho tôi.

Bất kỳ đề xuất/giải thích nào về điều này ??

Tôi đang sử dụng VS2008.

Cảm ơn !!

Trả lời

38

Như bạn hiện cho thấy chức năng init của bạn chấp nhận một chức năng không phải là thành viên. để làm điều đó như thế này:

static void Callback(int other_arg, void * this_pointer) { 
    CLoggersInfra * self = static_cast<CLoggersInfra*>(this_pointer); 
    self->RedundencyManagerCallBack(other_arg); 
} 

và gọi Init với

m_cRedundencyManager->Init(&CLoggersInfra::Callback, this); 

đó làm việc vì một con trỏ hàm để một hàm thành viên tĩnh không phải là một con trỏ hàm thành viên và do đó có thể bị xử lý như chỉ là một con trỏ với một chức năng miễn phí.

3

Đối số nào Init mất? Thông báo lỗi mới là gì?

Con trỏ phương pháp trong C++ hơi khó sử dụng. Bên cạnh con trỏ phương thức, bạn cũng cần cung cấp một con trỏ cá thể (trong trường hợp của bạn là this). Có lẽ Init mong đợi nó như là một đối số riêng biệt?

1

Tôi có thể thấy rằng init có ghi đè sau:

Init (CALLBACK_FUNC_EX callback_func, void * callback_parm)

nơi CALLBACK_FUNC_EX là typedef void (* CALLBACK_FUNC_EX) (int, void *);

3

Có phải m_cRedundencyManager có thể sử dụng các chức năng của thành viên không? Hầu hết các cuộc gọi lại được thiết lập để sử dụng các chức năng thông thường hoặc các hàm thành viên tĩnh. Hãy xem this page tại C++ FAQ Lite để biết thêm thông tin.

Cập nhật: Khai báo chức năng bạn đã cung cấp cho thấy m_cRedundencyManager đang mong đợi một chức năng có dạng: void yourCallbackFunction(int, void *). Do đó, các hàm thành viên không thể chấp nhận được dưới dạng gọi lại trong trường hợp này. Chức năng thành viên tĩnh có thể hoạt động, nhưng nếu điều đó là không thể chấp nhận trong trường hợp của bạn, mã sau cũng sẽ hoạt động. Lưu ý rằng nó sử dụng một diễn viên ác từ void *.


// in your CLoggersInfra constructor: 
m_cRedundencyManager->Init(myRedundencyManagerCallBackHandler, this); 

// in your CLoggersInfra header: 
void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr); 

// in your CLoggersInfra source file: 
void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr) 
{ 
    ((CLoggersInfra *)CLoggersInfraPtr)->RedundencyManagerCallBack(i); 
} 
2

Một con trỏ tới một hàm thành viên lớp là không giống như một con trỏ tới một hàm. Một thành viên của lớp có một đối số bổ sung tiềm ẩn (số con trỏ này) và sử dụng một quy ước gọi điện khác.

Nếu API của bạn mong đợi một hàm gọi lại không phải là thành viên, đó là những gì bạn phải chuyển đến.

0

Điều này question and answer từ C++ FAQ Lite bao gồm câu hỏi của bạn và những cân nhắc liên quan đến câu trả lời khá độc đáo tôi nghĩ.Đoạn trích ngắn từ trang web tôi đã liên kết:

Don’t.

Because a member function is meaningless without an object to invoke it on, you can’t do this directly (if The X Window System was rewritten in C++, it would probably pass references to objects around, not just pointers to functions; naturally the objects would embody the required function and probably a whole lot more).

+0

Liên kết hiện là https://isocpp.org/wiki/faq/pointers-to-members#memfnptr-vs-fnptr; có vẻ như bây giờ anh ta nói "Đừng". Đây là lý do tại sao các câu trả lời chỉ có liên kết là không tốt. –

+0

Hãy chỉnh sửa câu trả lời 8 tuổi của tôi :-) –

+0

Oh yeah ... duh :-) –

88

Đây là một câu hỏi đơn giản nhưng câu trả lời là phức tạp đáng ngạc nhiên. Câu trả lời ngắn gọn là bạn có thể làm những gì bạn đang cố gắng làm với std :: bind1st hoặc boost :: bind. Câu trả lời dài hơn là dưới đây.

Trình biên dịch chính xác để đề xuất bạn sử dụng & CLoggersInfra :: RedundencyManagerCallBack. Đầu tiên, nếu RedundencyManagerCallBack là một hàm thành viên, chính hàm đó không thuộc về bất kỳ cá thể cụ thể nào của lớp CLoggersInfra. Nó thuộc về chính lớp đó. Nếu bạn đã từng gọi một hàm lớp tĩnh trước đây, bạn có thể đã nhận thấy rằng bạn sử dụng cùng cú pháp SomeClass :: SomeMemberFunction. Vì bản thân hàm là 'tĩnh' theo nghĩa là nó thuộc về lớp chứ không phải là một cá thể cụ thể, bạn sử dụng cùng một cú pháp. '&' là cần thiết bởi vì về mặt kỹ thuật nói rằng bạn không chuyển trực tiếp các hàm - các hàm không phải là các đối tượng thực trong C++. Thay vào đó, bạn về mặt kỹ thuật truyền địa chỉ bộ nhớ cho hàm, đó là, một con trỏ trỏ đến nơi các lệnh của hàm bắt đầu trong bộ nhớ. Hệ quả là như nhau, bạn có hiệu quả 'truyền một hàm' như một tham số.

Nhưng đó chỉ là một nửa vấn đề trong trường hợp này. Như tôi đã nói, RedundencyManagerCallBack chức năng không 'thuộc' đối với bất kỳ cá thể cụ thể nào. Nhưng có vẻ như bạn muốn truyền nó như một cuộc gọi lại với một ví dụ cụ thể trong đầu. Để hiểu cách thực hiện điều này, bạn cần hiểu các hàm thành viên thực sự là gì: các hàm thường xuyên không được định nghĩa trong bất kỳ lớp nào có tham số ẩn thêm.

Ví dụ:

 

class A { 
public: 
    A() : data(0) {} 
    void foo(int addToData) { this->data += addToData; } 

    int data; 
}; 

... 

A an_a_object; 
an_a_object.foo(5); 
A::foo(&an_a_object, 5); // This is the same as the line above! 
std::cout 

Có bao nhiêu thông số không A :: foo mất? Thông thường chúng tôi sẽ nói 1. Nhưng dưới mui xe, foo thực sự mất 2. Nhìn vào định nghĩa của A :: foo, nó cần một ví dụ cụ thể của A để con trỏ 'this' có ý nghĩa (trình biên dịch cần biết cái gì ' đây là). Cách bạn thường chỉ định những gì bạn muốn 'này' được thông qua cú pháp MyObject.MyMemberFunction(). Nhưng đây chỉ là cú pháp cú pháp để truyền địa chỉ MyObject như tham số đầu tiên cho MyMemberFunction. Tương tự như vậy khi chúng ta khai báo các hàm thành viên bên trong các định nghĩa lớp, chúng ta không đặt 'this' trong danh sách tham số, nhưng đây chỉ là một món quà từ các nhà thiết kế ngôn ngữ để lưu kiểu gõ. Thay vào đó, bạn phải chỉ định rằng một hàm thành viên là tĩnh để chọn không tham gia nó tự động nhận thêm tham số 'this' này. Nếu ++ biên dịch C dịch ví dụ trên để mã C (bản gốc C++ làm việc thực tế như vậy), nó có lẽ sẽ viết một cái gì đó như thế này:

 

struct A { 
    int data; 
}; 

void a_init(A* to_init) 
{ 
    to_init->data = 0; 
} 

void a_foo(A* this, int addToData) 
{ 
    this->data += addToData; 
} 

... 

A an_a_object; 
a_init(0); // Before constructor call was implicit 
a_foo(&an_a_object, 5); // Used to be an_a_object.foo(5); 
 

Quay lại ví dụ của bạn, hiện nay là một vấn đề rõ ràng. 'Init' muốn một con trỏ trỏ đến một hàm nhận một tham số. Nhưng & CLoggersInfra :: RedundencyManagerCallBack là một con trỏ tới một hàm nhận hai tham số, đó là tham số bình thường và tham số bí mật 'this'. Vì vậy, tại sao bạn vẫn nhận được một lỗi trình biên dịch (như một lưu ý phụ: Nếu bạn đã từng sử dụng Python, loại nhầm lẫn này là lý do tại sao một tham số 'tự' là cần thiết cho tất cả các hàm thành viên).

Cách tiết kiệm để xử lý việc này là tạo đối tượng đặc biệt chứa con trỏ đến cá thể bạn muốn và có hàm thành viên được gọi là 'chạy' hoặc 'thực thi' (hoặc quá tải toán tử '()') lấy các tham số cho hàm thành viên và chỉ cần gọi hàm thành viên với các tham số đó trên cá thể được lưu trữ. Nhưng điều này sẽ yêu cầu bạn thay đổi 'Init' để lấy đối tượng đặc biệt của bạn chứ không phải là một con trỏ hàm thô, và có vẻ như Init là mã của người khác. Và tạo ra một lớp đặc biệt cho mỗi lần vấn đề này xuất hiện sẽ dẫn đến mã bloat.

Vì vậy, bây giờ, cuối cùng, giải pháp tốt, đẩy mạnh :: ràng buộc và tăng :: chức năng, tài liệu cho mỗi bạn có thể tìm thấy ở đây:

boost::bind docs, boost::function docs

boost :: ràng buộc sẽ cho phép bạn có một hàm, và một tham số cho hàm đó, và tạo một hàm mới trong đó tham số đó được 'khóa' tại chỗ. Vì vậy, nếu tôi có một hàm có thêm hai số nguyên, tôi có thể sử dụng boost :: bind để tạo một hàm mới trong đó một trong các tham số bị khóa để nói 5. Hàm mới này sẽ chỉ lấy một tham số nguyên và sẽ luôn thêm 5 cho nó. Sử dụng kỹ thuật này, bạn có thể 'khóa' tham số ẩn 'này' thành một cá thể lớp cụ thể và tạo một hàm mới chỉ lấy một tham số, giống như bạn muốn (lưu ý rằng tham số ẩn luôn là đầu tiên và tham số bình thường theo thứ tự sau nó). Nhìn vào boost :: bind docs cho các ví dụ, họ thậm chí còn thảo luận cụ thể về việc sử dụng nó cho các hàm thành viên. Về mặt kỹ thuật, có một hàm chuẩn được gọi là std :: bind1st mà bạn có thể sử dụng, nhưng boost :: bind là tổng quát hơn.

Tất nhiên, chỉ còn một lần nữa. boost :: bind sẽ tạo ra một hàm boost :: tốt cho bạn, nhưng đây vẫn là kỹ thuật không phải là một con trỏ hàm thô như Init có thể muốn. Rất may, boost cung cấp một cách để chuyển đổi boost :: function's thành con trỏ thô, như được ghi trên StackOverflow here. Làm thế nào nó thực hiện điều này là vượt ra ngoài phạm vi của câu trả lời này, mặc dù nó thú vị quá.

Đừng lo lắng nếu điều này có vẻ lố bịch - câu hỏi của bạn cắt một số góc tối hơn của C++ và tăng :: liên kết cực kỳ hữu ích khi bạn tìm hiểu.

+0

tldr. Dang nó! Phải gõ ít nhất 15 ký tự? –

+1

Đây là một câu trả lời tuyệt vời! – aardvarkk

+5

Đầu câu trả lời, std :: bind1st được đề xuất như một cách để thực hiện giải pháp, nhưng phần sau của câu trả lời là độc quyền về mặt tăng :: ràng buộc. Làm thế nào có thể std :: bind1st được sử dụng? – mabraham

4

Câu trả lời này là câu trả lời cho nhận xét ở trên và không hoạt động với VisualStudio 2008 nhưng nên được ưu tiên hơn với các trình biên dịch gần đây hơn.


Trong khi đó bạn không cần phải sử dụng một con trỏ void nữa và đó cũng là không có nhu cầu tăng từ std::bindstd::function có sẵn.Một lợi thế (so với con trỏ void) là gõ an toàn kể từ khi kiểu trả về và các đối số được quy định rõ ràng sử dụng std::function:

// std::function<return_type(list of argument_type(s))> 
void Init(std::function<void(void)> f); 

Sau đó, bạn có thể tạo các con trỏ hàm với std::bind và vượt qua nó để Init:

auto cLoggersInfraInstance = CLoggersInfra(); 
auto callback = std::bind(&CLoggersInfra::RedundencyManagerCallBack, cLoggersInfraInstance); 
Init(callback); 

Complete example cho việc sử dụng std::bind với thành viên, các thành viên tĩnh và hàm thành viên phi:

#include <functional> 
#include <iostream> 
#include <string> 

class RedundencyManager // incl. Typo ;-) 
{ 
public: 
    // std::function<return_type(list of argument_type(s))> 
    std::string Init(std::function<std::string(void)> f) 
    { 
     return f(); 
    } 
}; 

class CLoggersInfra 
{ 
private: 
    std::string member = "Hello from non static member callback!"; 

public: 
    static std::string RedundencyManagerCallBack() 
    { 
     return "Hello from static member callback!"; 
    } 

    std::string NonStaticRedundencyManagerCallBack() 
    { 
     return member; 
    } 
}; 

std::string NonMemberCallBack() 
{ 
    return "Hello from non member function!"; 
} 

int main() 
{ 
    auto instance = RedundencyManager(); 

    auto callback1 = std::bind(&NonMemberCallBack); 
    std::cout << instance.Init(callback1) << "\n"; 

    // Similar to non member function. 
    auto callback2 = std::bind(&CLoggersInfra::RedundencyManagerCallBack); 
    std::cout << instance.Init(callback2) << "\n"; 

    // Class instance is passed to std::bind as second argument. 
    // (heed that I call the constructor of CLoggersInfra) 
    auto callback3 = std::bind(&CLoggersInfra::NonStaticRedundencyManagerCallBack, 
           CLoggersInfra()); 
    std::cout << instance.Init(callback3) << "\n"; 
} 

thể đầu ra:

Hello from non member function! 
Hello from static member callback! 
Hello from non static member callback! 

Hơn nữa sử dụng std::placeholders bạn tự động có thể vượt qua đối số gọi lại (ví dụ điều này cho phép sử dụng return f("MyString"); trong Init nếu f có tham số chuỗi).

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