C++成員函數指針錯誤用法警示(成員函數指針與高性能的C++委托,三篇),附好多評論
今天做一個成績管理系統的並發引擎,用Qt做的,仿照QtConcurrent搞了個模板基類。這裏為了隱藏細節,隔離變化,把並發的東西全部包含在模板基類中。子類只需註冊需要並發執行的入口函數即可在單獨線程中執行。最終目標是,繼承的業務邏輯類外部調用時有兩個接口可選,調用syncRun同步執行;調用由引擎自動生成的asyncRun就異步執行。最終自動生成asyncRun的模板基類沒能實現,主要原因是mingw對this處理的太有問題了!!原本以為編譯器問題,後來才知道成員函數指針和this指針如此特殊,對此篇文章反感者請移步文章末尾直接看好文。
/*!
\author LiuBao
\date 2011/3/8
\brief 匯集了各種成員函數指針的錯誤用法
*/
#include <iostream>
#include <conio.h>
using namespace std;
class B;
class A
{
public:
A() {cout << "A::this->" << this << endl;}
void runA()
{
/* 錯誤的類型轉換,拜一篇爛文所賜用上的 */
union
{
void *from; //void*類型
void (A::*to)(int); //A的成員函數指針類型
void (*to2)(A*, int); //C的函數指針類型,第一個參數是A*類型,用於傳this
}ut;
ut.from = childFuncPtr;
(this->*ut.to)(0); //錯誤調用,用父類指針,以成員函數指針方式調用子類的成員函數
/* 微軟運行時庫檢測到運行時錯誤 */
ut.to2(this, 1); //錯誤調用,用C語言風格的函數指針,直接把this作為第一個參數傳入調用子類的成員函數
}
template<typename FromType>
void saveFuncPtr(FromType addr)
{
/* 錯誤的類型轉換,拜一篇爛文所賜用上的 */
union
{
FromType from; //任意成員函數指針類型
void *to; //void*類型
}ut;
/* 把地址轉換為void*保存到childFuncPtr */
ut.from = addr;
childFuncPtr = ut.to;
}
protected:
void *childFuncPtr; //錯誤使用,由於長度可能不同,void*不可以用來保存任意成員函數地址
};
class B : public A
{
public:
B()
{
cout << "B::this->" << this << endl; //打印B的this指針
this->saveFuncPtr(&B::testThis); //保存B::testThis地址到父類的void*成員變量
}
void runB()
{
/* 錯誤的類型轉換,拜一篇爛文所賜用上的 */
union
{
void *from; //void*類型
void (B::*to)(int); //類B的成員函數指針類型
void (*to2)(B*, int); //C語言的函數指針類型,第一個參數是B*類型,用於傳this
}ut;
ut.from = childFuncPtr;
testThis(2); //直接調用成員函數
(this->*ut.to)(3); //錯誤調用,用函數指針調用成員函數
/* 微軟運行時庫檢測到運行時錯誤 */
ut.to2(this, 4); //錯誤調用,用C語言風格的函數指針,直接把this作為第一個參數傳入調用子類的成員函數
}
void testThis(int i) {cout << i << " -> " << this << endl;}
};
int main()
{
B b;
b.runA();
b.runB();
_getch();
return 0;
}
本例旨在測試各編譯器對this的處理情況,其中有錯誤用法,請勿在實際項目中仿照使用!測試平臺Win7x64,各編譯器使用默認參數
gcc version 4.4.0 (GCC) 左debug右release。可見用debug版中,函數指針方法調用成員函數,成員函數中的this指針是錯的!
用於 80x86 的 Microsoft (R) 32 位 C/C++ 優化編譯器 16.00.30319.01 版左debug右release。debug版本有健全的運行時檢查,so,release時就可以放心的給出異常值了。vs處理C語言風格調用成員函數給出運行時錯誤,這一點很令人贊賞!
這是把用C語言風格調用成員函數兩處註釋掉後vs編譯運行結果,左debug右release。this指針完全正常。Intel(R) C++ Compiler XE for applications running on IA-32, Version 12.0.0.063 Build 20100721的運行結果與vs2010幾乎完全一樣,debug版本一樣有運行時錯誤,可見是微軟的運行時庫在起作用。但是release版本直接崩潰,也許跟優化方式有關?微軟自家編譯器鏈自家庫確實有優勢,呵呵~同樣,去掉兩處C風格調用,this指針完全正常。
Embarcadero C++ 6.31左debug右release。所有調用均輸出正確的this值!
總結:最費解的是mingw的結果(同學linux下用gcc測試結果一樣)。父類與子類有同樣的this值,同樣的函數地址,父類指針直接調用子類成員函數居然可以離譜成這樣!看來,奇技淫巧最終帶來的後果是各種不確定,不要嘗試用父類指針調用子類成員函數,更不要使用C語言的函數指針強制傳遞this指針!!
後續:現在才明白,我是試圖用模板實現自動類型推導的委托-_-! 推薦3篇該方面的好文:
成員函數指針與高性能的C++委托(上篇)
成員函數指針與高性能的C++委托(中篇)
成員函數指針與高性能的C++委托(下篇)
http://www.cnblogs.com/codingmylife/archive/2011/03/08/1976720.html
備註:在我使用的環境(VC6)裏,類的成員函數的默認調用約定是__thiscall; 關於這個調用約定,請註意在MSDN中的描述:“Arguments are pushed on the stack from right to left, with the this pointer being passed via register ECX, and not on the stack, on the x86 architecture.”
註意這段話表達的意思非常重要,在X86上,this指針是通過ECX傳遞的,而不是通過棧傳遞的。
在MSDN中還有比較重要的信息是,__thiscall 在VS2005.net之前的版本中無法顯示指定。
在IPF芯片和X64的機器上,__thiscall會被編譯器接受但是被忽略。所以我想這是你第一個測試環境沒有彈出錯誤對話框的原因。
http://blog.csdn.net/hifrog/archive/2004/07/03/33352.aspx
這篇裏“成員函數指針實現”一節提到的:
編譯器 選項 int DataPtr CodePtr Single Multi Virtual Unknown
MSVC 無 4 4 4 4 8 12 16
其中 void*屬於DataPtr,靜態成員函數指針也就是C語言的普通函數指針屬於CodePtr,Multi是多重繼承下的成員函數指針,Virtual是虛繼承下的成員函數指針。
如果我把多重繼承下的成員函數指針8字節長賦值給了void*(4字節長)就丟失了一部分數據。我沒試過,文中這麽寫的。
C++成員函數指針錯誤用法警示(成員函數指針與高性能的C++委托,三篇),附好多評論