2008-12-30 245 views
48

我正在使用一個API,要求我傳遞一個函數指針作爲回調。我試圖從我的班級使用這個API,但是我收到了編譯錯誤。如何傳遞一個類成員函數作爲回調?

以下是我從我的構造函數做的:

m_cRedundencyManager->Init(this->RedundencyManagerCallBack); 

這並不編譯 - 我得到以下錯誤:

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

我試過的建議,使用&CLoggersInfra::RedundencyManagerCallBack - 沒有爲我工作。

對此有何建議/解釋?

我正在使用VS2008。

謝謝!

回答

38

正如你現在顯示的你的init函數接受一個非成員函數。所以像這樣做:

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

,並調用初始化與

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

做是因爲一個函數指針的靜態成員函數是不是一個成員函數指針,因此可以像只是一個指針處理到一個免費的功能。

3

Init需要什麼參數?什麼是新的錯誤信息?

C++中的方法指針有點難以使用。除了方法指針本身,你還需要提供一個實例指針(在你的情況下爲this)。也許Init期望它作爲一個單獨的論點?

1

我可以看到,初始化具有以下控制裝置:

初始化(CALLBACK_FUNC_EX callback_func,無效* callback_parm)

其中CALLBACK_FUNC_EX是 無效的typedef(* CALLBACK_FUNC_EX)(INT,無效*);

3

m_cRedundencyManager能夠使用成員函數嗎?大多數回調都設置爲使用常規函數或靜態成員函數。有關更多信息,請參閱C++ FAQ Lite中的this page

更新:您提供的函數聲明顯示m_cRedundencyManager期待函數的形式如下:void yourCallbackFunction(int, void *)。因此,在這種情況下,成員函數作爲回調是不可接受的。一個靜態成員函數可能工作,但如果您的情況不能接受,下面的代碼也可以工作。請注意,它使用來自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

指向一個類的成員函數是不一樣的指針的函數。類成員需要一個隱含的額外參數(這個指針),並使用不同的調用約定。

如果你的API期望一個非成員回調函數,那就是你必須傳遞給它的東西。

0

question and answer來自C++ FAQ Lite涵蓋了你的問題和我認爲在答案中涉及的考慮相當好。我鏈接的網頁短片段:

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

該鏈接現在是https://isocpp.org/wiki/faq/pointers-to-members#memfnptr-vs-fnptr;看起來他現在說「不要」。這就是爲什麼只有鏈接的答案不好。 – 2016-01-12 19:45:11

+0

隨意編輯我八歲的答案:-) – 2016-01-12 22:06:25

88

這是一個簡單的問題,但答案非常複雜。簡短的答案是你可以做你想用std :: bind1st或boost :: bind做的事情。較長的答案在下面。

編譯器無誤建議您使用& CLoggersInfra :: RedundencyManagerCallBack。首先,如果RedundencyManagerCallBack是成員函數,則函數本身不屬於類CLoggersInfra的任何特定實例。它屬於類本身。如果你之前曾經調用過一個靜態類函數,你可能會注意到你使用了相同的SomeClass :: SomeMemberFunction語法。由於函數本身是屬於類而不是特定實例的「靜態」,因此您使用相同的語法。 '&'是必要的,因爲從技術上講,你不直接傳遞函數 - 函數不是C++中的真實對象。而是在技術上傳遞函數的內存地址,即指向函數指令在內存中的起始位置的指針。但結果是一樣的,你有效地'傳遞函數'作爲參數。

但在這種情況下,這只是問題的一半。正如我所說的,RedundencyManagerCallBack函數不屬於任何特定的實例。但是這聽起來像是你想把它作爲一個回調與一個特定的實例考慮在一起。要理解如何做到這一點,您需要了解成員函數的真正含義:常規未定義的任何類函數帶有額外的隱藏參數。

例如:

 

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 

多少參數時A :: foo的需要?通常情況下,我們會說1.但引導下,foo真的需要2個。考慮A :: foo的定義,它需要一個特定的A實例,以使'this'指針有意義(編譯器需要知道'這是)。通常,通過語法MyObject.MyMemberFunction()來指定要「this」的方式。但是這只是用於將MyObject的地址作爲MyMemberFunction的第一個參數傳遞的語法糖。同樣,當我們在類定義中聲明成員函數時,我們不會將'this'放入參數列表中,但這僅僅是語言設計者爲保存輸入而提供的禮物。相反,你必須指定一個成員函數是靜態的,以便自動退出它以獲得額外的'this'參數。如果C++編譯器編譯上面的例子C代碼(原來的C++編譯器實際工作這種方式),它可能會寫這樣的事:

 

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); 
 

回到你的榜樣,現在有一個明顯的問題。 'Init'需要一個指向一個參數的函數的指針。但是& CLoggersInfra :: RedundencyManagerCallBack是一個函數的指針,它接受兩個參數,它是普通參數和祕密'this'參數。因此,爲什麼你仍然得到一個編譯器錯誤(作爲一個方面說明:如果你曾經使用Python,這種混淆是爲什麼所有成員函數都需要'self'參數)。

處理這個問題的詳細方法是創建一個特殊對象,該對象包含一個指向您想要的實例的指針,並且具有一個名爲「run」或「execute」(或重載'()'操作符)的成員函數)它接受成員函數的參數,並簡單地使用存儲實例上的這些參數調用成員函數。但是這需要你改變'Init'來取你的特殊對象而不是一個原始的函數指針,而且聽起來像Init是別人的代碼。每次出現這個問題時都要做一個特殊的課程,這會導致代碼膨脹。

所以,現在,終於,很好的解決方案,提高::綁定和boost ::功能,每個的文檔,你可以在這裏找到:

boost::bind docsboost::function docs

的boost ::綁定將讓你需要一個函數和該函數的一個參數,並在該參數被鎖定的地方創建一個新函數。所以,如果我有一個函數添加兩個整數,我可以使用boost :: bind來創建一個新的函數,其中一個參數被鎖定爲5,這個新函數只需要一個整數參數,並且總是會添加5個到它。使用這種技術,您可以將隱藏的'this'參數鎖定爲特定的類實例,並生成一個只需要一個參數的新函數,就像您想要的那樣(請注意,隱藏參數始終是第一個參數,正常參數按順序排列)。看看boost :: bind docs的例子,他們甚至專門討論瞭如何將它用於成員函數。從技術上講,有一個稱爲std :: bind1st的標準函數,你也可以使用,但boost :: bind更通用。

當然,只有一個捕獲。 boost :: bind將會爲你提供一個很好的boost :: function,但是這在技術上仍然不像Init可能需要的原始函數指針。值得慶幸的是,boost提供了一種將boost :: function轉換爲raw指針的方法,如StackOverflow here中所記錄。它如何實現這個超出了這個答案的範圍,儘管它也很有趣。

不要擔心,如果這看起來很可笑 - 你的問題與C++的幾個較暗的角相交,而且boost :: bind一旦學習它就會非常有用。

4

此答案是對上述評論的回覆,不適用於VisualStudio 2008,但應與更新的編譯器一起使用。


同時您不必再使用一個空指針,也沒有必要,因爲提升和std::bindstd::function可用。一個優勢(相比於空指針)是因爲返回類型類型安全和參數使用std::function明確指出:

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

然後,您可以用std::bind創建函數指針,並把它傳遞給初始化:

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

Complete example使用std::bind用構件,靜態成員和非成員函數:

#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"; 
} 

可能的輸出:

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

此外使用std::placeholders可以動態參數傳遞給回調(例如如果f有一個字符串參數,則可以在Init中使用return f("MyString");)。

相關問題