成員指標


在〈函式指標〉介紹過如何將函式指定給對應型態的函式指標,類別的成員函式也是函式,必要時也可以指向對應型態的指標。

要宣告成員函式的指標,與非成員函式的指標宣告類似,主要是要以 :: 來指定是哪個類別的成員函式,函式簽署必須符合,以〈定義類別〉的 Account 為例,可以如下宣告:

void (Account::*mf1)(double) = nullptr;

mf1 = &Account::deposit;   
mf1 = &Account::withdraw;  

string (Account::*mf2)() = &Account::to_string; 

上例中 mf1 可以接受的是 Accountdepositwithdraw,而 mf2 可以接受的是 to_string,類別的實例會共用成員函式,呼叫成員函式時,必須將提供實例的位址給成員函式中的 this 指標,例如:

#include <iostream> 
#include <string>
#include "account.h"

void call(Account &self, void (Account::*member)(double), double param) {
    (self.*member)(param);
}

int main() {
    Account acct = {"123-456-789", "Justin Lin", 1000};

    call(acct, &Account::deposit, 1000);
    call(acct, &Account::withdraw, 500);

    cout << acct.to_string() << endl;

    return 0;
}

如果 self 是個指標,就要透過 ->,例如:

#include <iostream> 
#include "account.h"

void call(Account *self, void (Account::*member)(double), double param) {
    (self->*member)(param);
}

int main() {
    Account acct = {"123-456-789", "Justin Lin", 1000};

    call(&acct, &Account::deposit, 1000);
    call(&acct, &Account::withdraw, 500);

    cout << acct.to_string() << endl;

    return 0;
}

functional 標頭中定義有 mem_fn 函式,接受成員函式,傳回的呼叫物件,可以指定呼叫者收者,例如:

#include <iostream> 
#include <functional>
#include "account.h"

void call(Account &self, void (Account::*member)(double), double param) {
    mem_fn(member)(self, param);
}

int main() {
    Account acct = {"123-456-789", "Justin Lin", 1000};

    call(acct, &Account::deposit, 1000);
    call(acct, &Account::withdraw, 500);

    cout << acct.to_string() << endl;

    return 0;
}

指定呼叫者時可以是個值,這相當於指定 *this 參考的對象,也可以是個指標,這就是指定 this

#include <iostream> 
#include <functional>
#include "account.h"

void call(Account *self, void (Account::*member)(double), double param) {
    mem_fn(member)(self, param);
}

int main() {
    Account acct = {"123-456-789", "Justin Lin", 1000};

    call(&acct, &Account::deposit, 1000);
    call(&acct, &Account::withdraw, 500);

    cout << acct.to_string() << endl;

    return 0;
}

也許你會想起〈高階函式〉中的 bind 函式,它也可以用來綁定 this,例如:

#include <iostream> 
#include <functional>
using namespace std;
using namespace std::placeholders;

void call(Account &self, void (Account::*member)(double), double param) {
    bind<void>(member, &self, _1)(param);
}

int main() {
    Account acct = {"123-456-789", "Justin Lin", 1000};

    call(acct, &Account::deposit, 1000);
    call(acct, &Account::withdraw, 500);

    cout << acct.to_string() << endl;

    return 0;
}

為什麼要將 &self 當成是第一個參數呢?對於一個方法,例如 void Account::deposit(double amount),可以想像成編譯器將之轉換為 void Account::deposit(Account *this, double amount),而對於 acct.deposit(1000) 時,可以想像成編譯器將之轉換為 Account::deposit(&acct, 1000),實際上程式碼這麼寫不會編譯成功,因此才說是想像,然而可以透過 bind 來綁定第一個參數的值。

這就解答了另一個問題,怎麼使用 functionalfunction 模版來宣告成員函式型態呢?記得,第一個參數就是接受 this,因此就會是…

#include <iostream> 
#include <functional>
using namespace std;
using namespace std::placeholders;

void call(Account &self, function<void(Account*, double)> member, double param) {
    bind<void>(member, &self, _1)(param);
}

int main() {
    Account acct = {"123-456-789", "Justin Lin", 1000};

    call(acct, &Account::deposit, 1000);
    call(acct, &Account::withdraw, 500);

    cout << acct.to_string() << endl;

    return 0;
}

那麼 static 成員函式呢?在〈static 成員〉中談過,static 成員屬於類別,某些程度上,就是將類別當成是一種名稱空間,實際上與一般函式無異,因此,函式指標的宣告與一般函式無異:

double (*fn)(double) = Math::toRadian;

類似類別的成員函式指標,也可以宣告類別的資料成員指標,例如:

#include <iostream> 
using namespace std;

class Point {
public:
    int x;
    int y;
    Point(int x, int y) : x(x), y(y) {}
};

void printCord(Point &pt, int Point::*cord) {
    cout << pt.*cord << endl;
}

int main() {
    Point pt(10, 20);

    printCord(pt, &Point::x);    
    printCord(pt, &Point::y);    

    return 0;
}

在上例中,cord 是個資料成員指標,可以指向類別定義的資料成員,實際上要代表哪個實例的值域還需指定,同樣也可以透過 .*(參考的時候)、->*(指標的時候) 來使用。