標籤:

為什麼C++ 中 void * 能指向靜態成員函數但不能指非靜態向成員函數?

我在調用一個函數的時候需要給被調用的函數一個類型為 void* func的函數作為回調函數,於是發現只有在成員函數定義為static函數的時候能通過編譯。


C++ 並不保證所有的指針一樣大,很多情況下,指向非靜態成員的指針需要額外的空間來保存信息(虛函數、繼承都會帶來額外的偏移)

Pointers to member functions are very strange animals

Why the size of a pointer to a function is different from the size of a pointer to a member function?


讓我們來強行搞編譯器一波:

class A {

public:

void test() {

printf("A::test
");

}

};

typedef void (A::*FPA)();

typedef void(*FPVOID)();

int main()

{

union {

FPA _fpa;

FPVOID _fp_void;

}fp;

fp._fpa = (A::test); //make compiler happy!

A a;

A* pa = a;

(pa-&>*fp._fpa)();

(fp._fp_void)(); //因為A::test沒用到this,所以這麼搞也能工作。

return 0;

}


class Fuck
{
public:
void Shit(int, float){}
};

using Bitch = void(*)(Fuck*, int, float);
Bitch bitch = [](Fuck* fuck, int a, float b)
{
fuck-&>Shit(a, b);
};

只要多加一個參數就可以了,還是函數指針,也就還能變成void*,雖然不知道題主為什麼要這麼做。


因為有些編譯器的實現可能放不下


靜態成員函數跟靜態函數沒區別,就是一個作用域不同而已。可以看成普通的非成員函數。

成員函數在傳參數時候,實際上有個隱藏的this參數,跟靜態函數不一樣。靜態函數可以用函數指針調用,成員函數不行。


看了一下,似乎只有 @歐文韜 回答到了一個很根本的原因:成員函數指針的大小和void*指針的大小未必相同

看以下的代碼:

#include &
class A
{
public:
void func1(void)
{
}
static void func2(void)
{
}
};
int main(int argc, char** argv)
{
printf("%ld %ld %ld
", sizeof(A::func1), sizeof(A::func2), sizeof(void*));
return 0;
}

在64位g++下運行的結果是:16 8 8。

最後再明確一下:

在C++標準中,明確規定了普通指針(void*)和普通函數指針、類靜態成員函數指針的大小是相同的,因此他們之間可以無損的相互轉換。

而成員函數指針並沒有這樣的約束。所以,並不能保證在所有編譯器,所有場景下,都能和void*完成雙向轉換(沒記錯的話,vc的成員函數指針占空間會更小。例如上述代碼如果用vc編譯運行,結果應該是888,但目前手頭上沒有vc,無法驗證)。甚至不同的類型的類成員函數,還會有不同的大小。


首先: 回答題目中的問題,c++不允許把void*直接轉為成員函數,之所以靜態函數可以,是因為這兩個函數是不同的類型

舉個栗子:

void PrintNum(int num)
{
std::cout &<&< "function: " &<&< num &<&< std::endl; } class Foo { public: void MemberPrintNum(int num) { std::cout &<&< "member function: " &<&< num &<&< std::endl; } static void StaticPrintNum(int num) { std::cout &<&< "static function: " &<&< num &<&< std::endl; } };

PrintNum和StaticPrintNum的類型為:

void (*)(int num);

MemberPrintNum的函數類型為:

void (Foo::*)(int num);

成員函數調用需要隱式傳遞this指針,知道了類型之後,那麼如何使用就很簡單啦:

typedef void (*ptrFunc)(int num);
typedef void (Foo::*ptrMemFunc)(int num);

......

// 調用函數
ptrFunc pf = PrintNum;
(*pf)(5);

// 調用靜態函數
pf = Foo::StaticPrintNum;
(*pf)(5);

// 調用成員函數
Foo foo;
ptrMemFunc pmf = Foo::MemberPrintNum;
(foo.*pmf)(5);

然後,如果你手頭的編譯器是支持c++11的,那麼就有更簡單一點的寫法:

// 用std::function調用函數
std::function& fpn = PrintNum;
fpn(5);

// 用std::function調用成員函數
std::function& fmpn = Foo::MemberPrintNum;
fmpn(foo, 5);

剛剛測了一下,VS2015和gcc 5.4.0都可以順利通過編譯,但是VS2013上面的std::function直接綁定調用成員函數無法通過編譯。c++11還提供了許多比較方便的調用,具體可以看一下手冊,std::function和std::bind。

接著回答一下題主注釋中的問題,給被調用函數傳入一個函數作為回調函數:

c++11寫法:

void InvokeFuncCpp11(std::function& f, int num)
{
f(num);
}

......

// 傳入函數
std::function& fb = std::bind(PrintNum, std::placeholders::_1);
InvokeFuncCpp11(fb, 5);

// 傳入成員函數和類實例
std::function& fmb =
std::bind(Foo::MemberPrintNum, foo, std::placeholders::_1);
InvokeFuncCpp11(fmb, 5);

老式c++抖機靈寫法:

struct ptr_obj
{
void *obj;
union ptr_cast
{
ptrFunc pf;
ptrMemFunc pmf;
}pc;
};

......

void InvokeFunc(ptr_obj *po, int num)
{
if (po-&>obj == NULL)
{
(*(po-&>pc.pf))(num);
}
else
{
(((Foo*)po-&>obj)-&>*(po-&>pc.pmf))(num);
}
}

......

ptr_obj po;

// 調用函數
po.obj = NULL;
po.pc.pf = PrintNum;
InvokeFunc(po, 5);

// 調用成員函數
Foo foo;
po.obj = foo;
po.pc.pmf = Foo::MemberPrintNum;
InvokeFunc(po, 5);


非靜態方法,你不提供一個類對象給他,光拿著那個方法怎麼調用?

class Bitch {
int shit = 0;
public:
void fuck() { ++shit; }
}

假設按照題主的想法能夠成功,我們得到一個void (*func)(void)的函數指針,指向了Bitch::fuck()。

那當我們通過pFunc()的寫法調用這個函數指針時,請問程序去哪裡憑空變出一個Bitch對象的讓它去fuck,然後讓shit自增?

所以,要麼,你先構造好對象,通過std::bind,或者lambda,把對象和非靜態方法綁定為一個新的函數,就可以用了,這個函數必然操作這個對象。

Bitch mary;
std::function& shootMary = std::bind(Bitch::fuck, mary);
shootMary();
或者
Bitch mary;
typedef void (*PFunc)(void);
PFunc shootMary = [mary]() mutable { mary.fuck(); };
shootMary();

要麼,你就得多加一個參數,在調用時指定對哪個Bitch進行fuck操作。

Bitch mary;
Bitch lucy;
typedef void (Bitch::*PFunc)(void);
PFunc shoot = Bitch::fuck;
(mary.*shoot)();
(lucy.*shoot)();
或者
Bitch mary;
Bitch lucy;
typedef void (*PFunc)(Bitch*);
PFunc shoot = Bitch::fuck;
shoot(mary);
shoot(lucy);

PS:被輪子哥帶壞了……


因為成員函數是thiscall,有隱式傳遞的this指針

rust就沒有這個問題了


如果拋開 virtual 不談,類的靜態函數和非靜態函數編譯後地址是確定的,所以一定能通過 void * 來表示,只不過編譯器不允許顯式將一個非靜態類成員函數轉換成 void *(當然用 struct 曲線救國還是可以做到的),但是函數簽名中別忘了加上一個 this 指針作為第一個參數。


不找個對象就想干有對象才能幹的事兒?

想得美。


指向「成員函數」要用「成員函數指針」


說下淺顯易懂的吧,眾所周知類成員函數調用實際會隱式傳參:this。你一個void過去就把類型啥的丟了,就找不到真正的this了。


非靜態成員函數第一個參數是編譯器添加的為this指針


誰說不能啊,可以轉的


上百度查 std tr1 function,一定能得到你想要的結果


推薦閱讀:

程序員對自己的語言有沒有感情的?
蘋果系統為什麼用Objective-C,而不用C++?
為什麼盡量不要使用using namespace std?
如何delete數組?
c/cpp 中從源代碼到可執行文件的過程,鏈接是必須的嗎?

TAG:C |