這方法蠻特殊的,不知是否有實用的價值,很像為 Base 附加功能,不過的我寧願採用延伸介面的方法
1 #include <iostream>
2
3 using namespace std;
4
5 class Base1
6 {
7 public:
8 // Constructor
9 Base1(){}
10 // Destructor
11 ~Base1(){}
12 };
13
14 class Derived1:public Base1
15 {
16 int m_data;
17 public:
18 // Constructor
19 Derived1()
20 {
21 m_data = 0;
22 }
23 // Destructor
24 ~Derived1(){}
25
26 // Derived 的成員函數應有能力轉換 Base 的 this 指標
27 int Func(int i)
28 {
29 m_data += i;
30 return m_data;
31 }
32 };
33
34 int main()
35 {
36 Derived1 vDerived1;
37 Base1 vBase1;
38
39 // 成員函數指標似乎可以胡扯,不在乎是否有符合的函數存在
40 typedef int (Base1::*pmFunc_Ptr1)(int);
41 pmFunc_Ptr1 Func_Ptr1 = (pmFunc_Ptr1)&Derived1::Func; // 須強制轉型
42
43 cout << (vDerived1.*Func_Ptr1)(1) << endl; // 正確執行
44
45 // Base1 中並無任何函數符合 pmFunc 要求的形式,照樣給它跑,
46 // 但得到錯誤的回傳值,而且 Base1 並沒有 m_data,因此寫入的動作很危險
47 cout << (vBase1.*Func_Ptr1)(1) << endl;
48
49 typedef int (Derived1::*pmFunc_Ptr2)(int);
50 pmFunc_Ptr2 Func_Ptr2 = &Derived1::Func;
51
52 cout << (vDerived1.*Func_Ptr2)(1) << endl;
53
54 // cout << (vBase1.*Func_Ptr2)(1) << endl; // 編譯不過
55
56
57 return 0;
58 }
再補上一篇,
這次應更能看出 成員函數指標 的奇怪能力
1 #include <iostream>
2
3 using namespace std;
4
5 class Base1
6 {
7 int i; // 加上這個才能 Base1 Base2 位置不同
8 public:
9 // Constructor
10 Base1(){}
11 // Destructor
12 ~Base1(){}
13 };
14
15 class Base2
16 {
17 int i; // 加上這個才能 Base1 Base2 位置不同
18 public:
19 // Constructor
20 Base2(){}
21 // Destructor
22 ~Base2(){}
23 };
24
25 class Derived1:public Base1, public Base2
26 {
27 int m_data;
28 public:
29 // Constructor
30 Derived1()
31 {
32 m_data = 0;
33 }
34 // Destructor
35 ~Derived1(){}
36
37 // Derived 的成員函數應有能力轉換 Base 的 this 指標
38 int Func(int i)
39 {
40 cout << "Derived1's this Addr = " << this << endl;
41 cout << "&m_data = " << &m_data << endl;
42 m_data += i;
43 return m_data;
44 }
45 };
46
47 // 成員函數指標似乎可以胡扯,不在乎是否有符合的函數存在
48 typedef int (Base1::*pmFunc_Base1_Ptr)(int);
49
50 // Obj 是 Base1 延伸類別產生的物件
51 // Func 須是 Obj 的成員函數
52 void test1(Base1 *Obj, pmFunc_Base1_Ptr Func)
53 {
54 cout << "Base1 Addr = " << Obj << endl;
55 cout << (Obj->*Func)(1) << endl;
56 }
57
58
59 // 成員函數指標似乎可以胡扯,不在乎是否有符合的函數存在
60 typedef int (Base2::*pmFunc_Base2_Ptr)(int);
61
62 // Obj 是 Base2 延伸類別產生的物件
63 // Func 須是 Obj 的成員函數
64 void test2(Base2 *Obj, pmFunc_Base2_Ptr Func)
65 {
66 cout << "Base2 Addr = " << Obj << endl;
67 cout << (Obj->*Func)(1) << endl;
68 }
69
70
71 int main()
72 {
73 Derived1 vDerived1;
74
75 pmFunc_Base1_Ptr Base1_Func_Ptr = (pmFunc_Base1_Ptr)&Derived1::Func; // 須強制轉型
76 pmFunc_Base2_Ptr Base2_Func_Ptr = (pmFunc_Base2_Ptr)&Derived1::Func; // 須強制轉型
77
78 cout << "vDerived1's Addr = " << &vDerived1 << endl;
79 cout << (vDerived1.*Base1_Func_Ptr)(1) << endl; // 正確執行
80 test1(&vDerived1, Base1_Func_Ptr);
81 test2(&vDerived1, Base2_Func_Ptr);
82
83 }
順便貼上執行結果,編譯器是 gcc 4.6.1。
Base1 Addr 和 Base2 Addr 位址是不一樣的,但都能正確執行 Derived1::Func()
vDerived1's Addr = 0x22ff44
Derived1's this Addr = 0x22ff44
&m_data = 0x22ff4c
1
Base1 Addr = 0x22ff44
Derived1's this Addr = 0x22ff44
&m_data = 0x22ff4c
2
Base2 Addr = 0x22ff48
Derived1's this Addr = 0x22ff44
&m_data = 0x22ff4c
3
以下貼文轉自
http://www.programmer-club.com.tw/showsametitleN/c/45694.html 的討,經由討論和測試證實是在手動轉型動手腳
-----------------------------------------------
首先要說的是 父子類別 的關係,就比較超然的高度來看,也就是 oo 的觀點來看,就如 R 大說的「任何指向 Derived1 的指標同時也指向 Base1, 因為 Derived1 包括 Base1」,但實務來看,也就是 oop 的觀點來看,物件執行時期的依據是 this 指標,一個物件是多個類別組成的,各類別執行時期的區分就靠 this 指標,
- 也許各類別 this 的位址恰巧相同,但也有可能不同。
- C++ 執行時期,可由子類別去呼叫父類別,因有子必有父。
- C++ 執行時期,不能可由父類別去呼叫子類別,因它不一定有兒子,但由呼叫自己的虛擬函數有可能可以呼叫到子類別,那就算是後繼有人。
- 不管是父叫子還是子叫父,都必須假設 this 指標的位址可能會不一樣,因此有可能會有調整 this 的動作
- 由子叫父,因清楚父的模樣,因此編譯器在呼叫父前可以很容易先多加調整 this 的程式碼
- 由父叫子是經由虛擬函數表,虛擬函數表是是由編譯器做的,使用這表的 this 和被呼叫的函數的 this 間的差距編譯器也很清楚,因此可先執行調整程式碼再進入子類別
再來看看 成員函數指標 算什麼,就我給的兩個例子來看,就像是父類別的虛擬函數,可以正確的叫用子類別,由以上說明可知,那 調整程式碼 是從哪來的?Derived1::Func() 只是普通成員函數,R 大也說 "這裡沒有「轉換」的成分",但總得有地方做手腳吧,我看來看去最有可能在以下這地方,
pmFunc_Ptr1 Func_Ptr1 = (pmFunc_Ptr1)&Derived1::Func; // 須強制轉型
因只有這裡能讓編譯器知道父子各是誰,可以為它準備好調整程式碼,若我猜得沒錯 Func_Ptr1 得到的不是 Derived1::Func() 的位址,而是像虛擬函數表一樣,先指向調整程式碼,可是看看 Derived1::Func() 只是普通的成員函數,只能被本身及子類別呼叫,一般只會做子到父的調整程式碼,現在很貼心的做父到子的調整程式碼,因此這個 強制轉型 根本就是使用這個超智能指標的標準動作嘛,想想編譯器擋掉自動轉型已是盡了把關的責任,手動的強制轉型已表示程式設計師知道在做什麼
往更深層的觀點來看,你不用在父類別準備虛擬函數,就能任意為父類別擴增功能,這自由度不是更高,我說過 "成員函數指標似乎可以胡扯,不在乎是否有符合的函數存在",其實是讚美之意。
還有不知這算不算是標準,在 gcc 是沒問題,vs2008 就不行了
沒有留言:
張貼留言