指向類成員函數的指針並非指針
參考<<C++必知必會>>的相關章節
"指向類成員函數的指針",這個術語中包含了"類成員函數"的術語,但是嚴格的說,這里的成員函數只是指非靜態成員函數,這個術語中還包含了"指針"這個術語,
但是嚴格的說,它即不包含地址,行為也不象指針,說得干脆點,那就是"指向類成員函數的指針"並非指針.盡管這個術語有很大的迷惑性,但是就其含義來說,
可以把一組同類型的函數抽象為一個"指向函數的指針",同樣的道理,可以把一組類中同類型的類成員函數抽象為一個"指向類成員函數的指針",兩者是一致的
"指向類成員函數的指針"和"指向函數的指針"有什么區別?從字面上就可以清楚的知道,前者是和類,對象相關的,而后者直接指向函數的地址
我們首先復習一下"指向函數的指針"如何使用?
void print()
{
}
void (*pfun)(); //聲明一個指向函數的指針,函數的參數是void,函數的返回值是void
pfun = print; //賦值一個指向函數的指針
(*pfun)(); //使用一個指向函數的指針
比較簡單,不是嗎?為什么*pfun需要用()擴起來呢?因為*的運算符優先級比()低,如果不用()就成了*(pfun())
"指向類成員函數的指針"比"指向函數的指針"就多了個類的區別:
struct CPoint
{
void plus(double x_, double y_){}
void minus(double x_, double y_){}
void mul(double x_, double y_){}
void dev(double x_, double y_){}
virtual void move(double x_, double y_){}
double x;
double y;
};
void Oper(CPoint* pPoint, void (CPoint::*pfun)(double x_, double y_), int x, int y)
{
(pPoint->*pfun)(x, y);
}
struct CPoint3d : public CPoint
{
void move(double x_, double y_){ }
};
int main(int argc, char* argv[])
{
CPoint pt;
void (CPoint::*pfun)(double x_, double y_);
int offset = 0;
pfun = &CPoint::plus;
offset = (int&)pfun;
(pt.*pfun)(10, 10);
Oper(&pt, pfun, 10, 10);
pfun = &CPoint::minus;
offset = (int&)pfun;
(pt.*pfun)(10, 10);
Oper(&pt, pfun, 10, 10);
pfun = &CPoint::move;
offset = (int&)pfun;
(pt.*pfun)(10, 10);
Oper(&pt, pfun, 10, 10);
CPoint3d pt3d;
void (CPoint3d::*p3dfun)(double x_, double y_);
p3dfun = &CPoint3d::move;
(pt3d.*p3dfun)(10, 10);
//p3dfun = pfun; //正確
//pfun = p3dfun; //錯誤
pfun = (void (CPoint::*)(double, double))p3dfun;
Oper(&pt3d, (void (CPoint::*)(double, double))p3dfun, 10, 10);
return 0;
}
void (CPoint::*pfun)(double x_, double y_);
這里是"指向類成員函數的指針"的聲明,就是多了CPoint::的限定
pfun = &CPoint::plus;
這里是"指向類成員函數的指針"的賦值,在賦值的時候必須用這種靜態的方式
(pt.*pfun)(10, 10);
這里是"指向類成員函數的指針"的使用,記住,解引用必須有實際的this指針地址,因此必須用有地址的對象pt來解引用,.*的語法有些怪異,不過我寧願把它拆解為pt.和*pfun兩部分來理
解
offset = (int&)pfun;
這里offset=4198410,當然不同的項目,不同的編譯器這個值是不同的,由此也可以知道,"指向類成員函數的指針"確實是一個指針,其實由C++對象模型我們就應該知道這個結論了
,在C++對象模型中,成員函數是全局的,並不屬於對象
有人想用這個值嗎?或許可以用下面的代碼:
void (CPoint::*pfun2)(double x_, double y_);
memcpy(&pfun2, &offset, sizeof(int));
Oper(&pt, pfun2, 10, 10);
不過,我還是忍不住奉勸各位,盡量不要直接使用這個值,這畢竟是編譯器內部實現的細節,實在有太多的人喜歡這種黑客似的代碼並四處炫耀,真正的"指向類成員函數的指針"
的用法只應該包括聲明,賦值和解引用
pfun = &CPoint::move;
注意到這里的move是虛函數,那么這里還支持虛函數的多態嗎?沒有問題,"指向類成員函數的指針"支持多態,當然了,代價是,這時候這個指針就必須擴展為一個結構了,C++為了
"指向類成員函數的指針"支持多態是要付出代價的
p3dfun = pfun; //正確
存在基類的"指向類成員函數的指針"到派生類的"指向類成員函數的指針"的隱式轉換,其含義無疑是說基類的成員函數布局信息只是派生類中成員函數布局信息的一個子集,
因此這樣的轉換應該是沒有問題的,但是反過來呢?
//pfun = p3dfun; //錯誤
不存在派生類的"指向類成員函數的指針"到基類的"指向類成員函數的指針"的隱式轉換,因為派生類中的成員函數並不一定能夠在基類中找到
"指向類成員函數的指針"基類和派生類的關系和"指向類對象的指針"基類和派生類的關系完全相反,就"指向類成員函數的指針"的本質來說,這是合理的,但是這樣的話,
我們就無法利用公共的Oper函數了,除非...
pfun = (void (CPoint::*)(double, double))p3dfun; //強制轉換
我們做強制轉換是可以的
Oper(&pt3d, (void (CPoint::*)(double, double))p3dfun, 10, 10);
而且也只有強制轉換才可以利用公共的Oper函數了,這里的Oper調用的是pt3d中的move函數,沒有錯誤的
但是是否一定要這樣做呢?這取決於程序員自己的選擇
【實驗】
#include <iostream>
using namespace std;
struct testClass{
void testFun(){cout<<"testFun()\n";}
};
int main(int argc, char* argv[])
{
void(testClass::*p)();
p=&testClass::testFun;
long offset=(long&)p;
void (*pfun) ();
memcpy(&pfun,&offset,sizeof(offset));
(*pfun)();
return 0;
}