為什麼C++ 中 void * 能指向靜態成員函數但不能指非靜態向成員函數?
我在調用一個函數的時候需要給被調用的函數一個類型為 void* func的函數作為回調函數,於是發現只有在成員函數定義為static函數的時候能通過編譯。
C++ 並不保證所有的指針一樣大,很多情況下,指向非靜態成員的指針需要額外的空間來保存信息(虛函數、繼承都會帶來額外的偏移)
Pointers to member functions are very strange animalsWhy 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);
};
因為有些編譯器的實現可能放不下
靜態成員函數跟靜態函數沒區別,就是一個作用域不同而已。可以看成普通的非成員函數。成員函數在傳參數時候,實際上有個隱藏的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(5);
// 用std::function調用成員函數 剛剛測了一下,VS2015和gcc 5.4.0都可以順利通過編譯,但是VS2013上面的std::function直接綁定調用成員函數無法通過編譯。c++11還提供了許多比較方便的調用,具體可以看一下手冊,std::function和std::bind。
std::function&
fmpn(foo, 5);
void InvokeFuncCpp11(std::function&
{
f(num);
}
......
// 傳入函數
std::function&
InvokeFuncCpp11(fb, 5);
// 傳入成員函數和類實例
std::function&
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();
或者
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);
因為成員函數是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 |