Linux中國

C++ 類成員函數指針語法的友好指南

如果你正在尋找性能、複雜性或許多可能的解決方法來解決問題,那麼在涉及到極端的情況下,C++ 總是一個很好的選擇。當然,功能通常伴隨著複雜性,但是一些 C++ 的特性幾乎難以分辨。根據我的觀點,C++ 的 類成員函數指針 也許是我接觸過的最複雜的表達式,但是我會先從一些較簡單的開始。

文章中的例子可以在我的 Github 倉庫 里找到。

C 語言:函數指針

讓我們先從一些基礎開始:假設你有一個函數接收兩個整數作為參數返回一個整數:

int sum(int a, int b) {
    return a+b;
}

在純 C 語言中,你可以創建一個指向這個函數的指針,將其分配給你的 sum(...) 函數,通過解引用來調用它。函數的簽名(參數、返回類型)必須符合指針的簽名。除此之外,一個函數指針表現和普通的指針相同:

int (*funcPtrOne)(int, int);

funcPtrOne = ∑

int resultOne = funcPtrOne(2, 5);

如果你使用指針作為參數並返回一個指針,這會顯得很醜陋:

int *next(int *arrayOfInt){
    return ++arrayOfInt;
}

int *(*funcPtrTwo)(int *intPtr);

funcPtrTwo = &next;

int resultTwo = *funcPtrTwo(&array[0]);

C 語言中的函數指針存儲著子程序的地址。

指向類成員函數的指針

讓我們來進入 C++:好消息是你也許不需要使用類成員函數指針,除非在一個特別罕見的情況下,比如說接下來的例子。首先,你已經知道定義一個類和其中一個成員函數:

class MyClass
{
public:

    int sum(int a, int b) {
        return a+b;
    }

};

1、定義一個指針指向某一個類中一個成員函數

聲明一個指針指向 MyClass 類成員函數。在此時,你並不知道想調用的具體函數。你僅僅聲明了一個指向 MyClass 類中任意成員函數的指針。當然,簽名(參數、返回值類型)需要匹配你接下想要調用的 sum(...) 函數:

int (MyClass::*methodPtrOne)(int, int);

2、賦值給一個具體的函數

為了和 C 語言(或者 靜態成員函數)對比,類成員函數指針不需要指向絕對地址。在 C++ 中,每一個類中都有一個虛擬函數表(vtable)用來儲存每個成員函數的地址偏移量。一個類成員函數指針指向 vtable 中的某個條目,因此它也只存儲偏移值。這樣的原則使得 多態 變得可行。

因為 sum(...) 函數的簽名和你的指針聲明匹配,你可以賦值簽名給它:

methodPtrOne = &MyClass::sum;

3、調用成員函數

如果你想使用指針調用一個類成員函,你必須提供一個類的實例:

MyClass clsInstance;
int result = (clsInstance.*methodPtrOne)(2,3);

你可以使用 . 操作符來訪問,使用 * 對指針解引用,通過提供兩個整數作為調用函數時的參數。這是醜陋的,對吧?但是你可以進一步應用。

在類內使用類成員函數指針

假設你正在創建一個帶有後端和前端的 客戶端/伺服器 原理架構的應用程序。你現在並不需要關心後端,相反的,你將基於 C++ 類的前端。前端依賴於後端提供的數據完成初始化,所以你需要一個額外的初始化機制。同時,你希望通用地實現此機制,以便將來可以使用其他初始化函數(可能是動態的)來拓展你的前端。

首先定義一個數據類型用來存儲初始化函數(init)的指針,同時描述何時應調用此函數的信息(ticks):

template<typename T>
struct DynamicInitCommand {
    void (T::*init)();     // 指向額外的初始化函數
    unsigned int ticks;    // 在 init() 調用後 ticks 的數量
};

下面一個 Frontend 類示例代碼:

class  Frontend
{
public:

    Frontend(){
        DynamicInitCommand<Frontend> init1, init2, init3;

        init1 = { &Frontend::dynamicInit1, 5};
        init2 = { &Frontend::dynamicInit2, 10};
        init3 = { &Frontend::dynamicInit3, 15};

        m_dynamicInit.push_back(init1);
        m_dynamicInit.push_back(init2);
        m_dynamicInit.push_back(init3);
    }

    void  tick(){
        std::cout << "tick: " << ++m_ticks << std::endl;

        /* 檢查延遲初始化 */
        std::vector<DynamicInitCommand<Frontend>>::iterator  it = m_dynamicInit.begin();

        while (it != m_dynamicInit.end()){
            if (it->ticks < m_ticks){

                if(it->init)
                    ((*this).*(it->init))(); // 這裡是具體調用

                it = m_dynamicInit.erase(it);

            } else {
                it++;
            }
        }
    }

    unsigned  int  m_ticks{0};

private:

    void  dynamicInit1(){
        std::cout << "dynamicInit1 called" << std::endl;
    };

    void  dynamicInit2(){
        std::cout << "dynamicInit2 called" << std::endl;
    }

    void  dynamicInit3(){
        std::cout << "dynamicInit3 called" << std::endl;
    }

    unsigned  int  m_initCnt{0};
    std::vector<DynamicInitCommand<Frontend> > m_dynamicInit;
};

Frontend 完成實例化後,tick() 函數會被後端以固定的時間時間調用。例如,你可以每 200 毫秒調用一次:

int  main(int  argc, char*  argv[]){
    Frontend frontendInstance;

    while(true){
        frontendInstance.tick(); // 僅用於模擬目的
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
    }
}

Fronted 有三個額外的初始化函數,它們必須根據 m_ticks 的值來選擇調用哪個。在 ticks 等於何值調用哪個初始化函數的信息存儲在數組 m_dynamicInit 中。在構造函數(Frontend())中,將此信息附加到數組中,以便在 5、10 和 15 個 tick 後調用其他初始化函數。當後端調用 tick() 函數時,m_ticks 值會遞增,同時遍曆數組 m_dynamicInit 以檢查是否必須調用初始化函數。

如果是這種情況,則必須通過引用 this 指針來取消引用成員函數指針:

((*this).*(it->init))()

總結

如果你並不熟悉類成員函數指針,它們可能會顯得有些複雜。我做了很多嘗試和經歷了很多錯誤,花了一些時間來找到正確的語法。然而,一旦你理解了一般原理後,方法指針就變得不那麼可怕了。

這是迄今為止我在 C++ 中發現的最複雜的語法。 你還知道更糟糕的嗎? 在評論中發布你的觀點!

via: https://opensource.com/article/21/2/ccc-method-pointers

作者:Stephan Avenwedde 選題:lujun9972 譯者:萌新阿岩 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國