類成員指針和成員函數指針(深入理解)

類成員指針和成員函數指針(深入理解

         本想實現一個boost的bind,然而在綁定類成員函數時引出了成員函數指針這麼個東西,通過深入的理解之後,又引出了類成員指針這麼個東西。通過學習對他們有了深刻的理解,現在做一個總結如下,以示區分。(更多參考可見《c++必知必會》中第15,16節)

1.類成員指針

1)類成員指針到底是神馬東東

”指向類成員的指針“這個描述中有“指針”這個術語,它其實既不包含地址,行爲也不像指針。怎麼理解呢?與常規指針不同,一個指向類成員的指針並不指向一個具體的內存位置,它指向的是一個類的特定成員,而不是指向一個特定對象的特定成員。通常最清楚的說法是:把數據成員的指針看做爲一個偏移量,它告訴你, 一個特定成員的位置距離對像的起點有多少個字節。而且在大多數編譯器中都把數據成員的指針實現爲一個整數,其中包含被指向成員的偏移量,另外在加1(這裏是爲了讓0值也可以表示一個空的數據成員)。

舉個例子:

class test_t
{
	public:
		int m_value;
};

int test_t::*mem_ptr;

mem_ptr ptr = &test_t::m_value;
ptr設置成&test_t::m_value時,實際上是將ptr設置成m_value在test_t類上的偏移量。
test_t test;
test.*ptr = 2
當寫下test.*ptr時,實際上是請求將test對象的地址加上數據成員的偏移量,爲的就是訪問這個數據成員。
2)在繼承關係中的應用法則

在C++中,存在指向派生類的指針向指向基類的預定義轉換,但是在指向成員指針的情況下則恰恰相反:存在指向基類的類成員指針向指向公有派生類的成員指針的轉化,反過來則會出錯。如果理解成員指針其實是類成員的相對類對象地址的一個偏移量,這樣對於上面的法則就會很容易理解。例如:

class test2_t:public test_t
{
	public:
		float m_value2;
}
int test2_t::*mem_ptr = &test_t::m_value;//ok,從基類到派生類的轉化
int test_t::*mem_ptr = &test2_t::m_value2;//error,從派生類到基類的轉化


因爲test2_t對象中包含test_t對象的對象,所以在test_t對象上的任何偏移在test2_t對象上是有效的,
然而反過來就未必是一個有效的偏移量。


2.類成員函數指針

1)類成員函數指針是神馬東東

獲取非靜態成員函數的地址時,得到的不是一個地址,而是一個指向成員函數的指針。怎麼理解呢?如果你關注的了《深入C++對象模型》你就會明白,對於類的所有非靜態成員函數,在同一個進程內,在代碼段中存在且只存在一份,所有類的對象在調用同一成員數據都是調用的這個份,但是必須傳入一個隱含的this指針來訪問相關數據成員。所以成員函數指針指向的就是這個代碼空間。

成員函數指針跟普通的函數指針是有很大區別的,縱然返回值和參數在一樣的情況下。一個指向成員函數的指針的實現必須存儲一些信息,比如它所指向的成員函數是虛擬的還是非虛擬的,以及到哪裏去查找虛函數表,如果涉及數據成員,還要通過this來計算偏移量等等。。總之:成員函數有一個非成員函數不具有的屬性---它的類信息。成員函數的指針必須與向其賦值的函數類型匹配,不是兩方面,而是三個方面都要匹配:a:參數類型和個數b:返回類型c:它所屬的類型(its class)

2)在繼承關係中的應用法則

和數據成員指針一樣,指向成員函數的指針也表現出一定的逆變性,即存在從指針基類的成員函數指針向指向派生類的成員函數指針的轉化,反之則不然。因爲基類成員函數會試圖通過其this訪問基類成員,然而派生類函數可能會試圖訪問基類中不存在的成員。如:

class B
{
	public:
		void bset(int val_)
		{
			m_bval = val_;
		}
	private:
		int m_bval;
		
}

class D:public B
{
	public:
		void dset(int val_)
		{
			m_dval = val_;
		}
	private:
		int m_dval;
		
}
B b;
D d;
void (B::*f1)(int) = &D::dset;
(b.*f1)(12); // error, 訪問不存在的dval成員。
void (D::*f2)(int) = &B::bset;
(d.*f2)(11);//OK,設置繼承來的m_bval成員。

最後,根據以上分析寫了點測試代碼,包含了靜態成員函數指針,有幾處輸出可能不太明白,請高人指點!

#include <iostream>
#include <stdio.h>
using namespace std;

class test_t
{
    public:
        void print1()
        {   
            cout << "hello:"<< endl;
        }   
        static void print2()
        {   
            cout << "hello:"<< endl;
        }   
        int m_value;
        static int g_data;
};

int test_t::g_data = 2;

typedef void (test_t::*func_ptr)();
typedef int test_t::*mem_ptr;

int main()                                                                                                                                                   
{ 
    mem_ptr ptr = &test_t::m_value;
    printf("%p\n", ptr); //nil,這個地方出nil不明白。
    cout << &test_t::m_value<< endl;//1,輸出1不明白
    cout << ptr << endl;//1, 輸出1不明白
    cout << &ptr << endl;//0x7fffe3376078

    printf("%p\n", &test_t::print1);//0x400ade
    cout << &test_t::print1 << endl;//1,輸出1不明白,難道偏移量是1?


    int* tmp_ptr = &test_t::g_data;
    cout << *tmp_ptr << endl;//2
        
    typedef void (*func)();
    func tmp = &test_t::print2;
    printf("%p\n", tmp);//0x400abc
    cout << &tmp << endl;//0x7fffe3376070
}








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章