第57章 メンバ関数ポインタ天国

 ポインタ天国最終章、メンバ関数ポインタ天国の始まり始まり〜。


 では、今回の要点です。


 では、いってみましょう。


 さて。第9章で関数ポインタについて話しました。その冒頭でこういう風に話しました。

関数もアドレスが取れると言いました。アドレスはただの数値です。となると関数のアドレスを入れることのできる変数があってもいいじゃないですか。今回はそういう変数についてのお話です。

 これはもちろんメンバ関数にも言えることで、メンバ関数ポインタというのも作ることができます。

 では、どんな文法なのかちょっと考えてみましょう。恒例ですね。

 先ず、普通の関数ポインタは次のようになるのでしたね。

void Func();
void (*fpFunc)() = Func;

void *fpFunc(); だと戻り値の型が void* の関数のプロトタイプと判断されるのでカッコをつけるのでした。

 では、先ずはこれにメンバ関数のアドレスを代入できるか試してみましょう。

class CTest { public: void Func(); };
void (*fpFunc)() = CTest::Func;

error C2440: 'void (__thiscall CTest::*)(void)' から 'void (__cdecl *)(void)' に変換することはできません。

 はい。エラーになりました。この理由は第2部第30章で話したことを踏まえれば簡単なことです。メンバ関数には this ポインタが隠し引数として渡されるので、普通の関数と同じようには扱えないのです。また、同時に静的メンバ関数なら普通の関数ポインタに代入できるということも分かると思います。

class CTest { public: static void Func(); };
void (*fpFunc)() = CTest::Func;  // OK

 これで、普通の関数ポインタに何かを付け足す必要があることが分かりました。では、何を付け足せばいいのでしょうか?

 メンバ関数ポインタを使って関数を呼ぶときには、同時にオブジェクトを指定します。これが隠し引数である this ポインタになるわけですね。このとき、全く無関係なクラスのメンバ関数が呼べてしまうと困ります

 つまり、メンバ関数ポインタにはクラス名も指定してやる必要があることになりますね。

 問題はどこにどのように付け足すかです。「どのように」というのは、クラス名を指定するときのお約束で CTest:: のようなアクセス解決で良さそうです。

 「どこに」ですが、これは簡単に2つ考えられます。

void (CTest::*fpFunc)();
void (*CTest::fpFunc)();

 しかし、static メンバ変数としてメンバ関数ポインタを作った時を考えると、後者の方では問題があることが分かります。

class CTest2
{
public: static void (CTest::*m_fpFund)();
};
static void (CTest::*CTest2::m_fpFund)();

class CTest2
{
public: static void (*CTest::m_fpFund)();
};
static void (*CTest::CTest2::m_fpFund)();
static void (*CTest2::CTest::m_fpFund)();  // どっち??

 ということで、void (CTest::*fpFunc)(); が正しいメンバ関数ポインタになりそうです。では、確認してみましょう。

class CTest { public: void Func(); };
void (CTest::*fpFunc)() = CTest::Func;

 はい。問題なくコンパイルできたと思います。このように、メンバ関数ポインタの文法は

<戻り値の型> (<クラス名>::*<変数名>(<引数リスト>);

となります。型名は、やはり名前を除いた <戻り値の型> (<クラス名>::*)(<引数リスト>) となります。


 さて、メンバ関数ポインタを使いたいのですが、まだ分からないことがあります。それはどうやって呼ぶかです。

 先ず、

fpFunc();

のように呼んでもダメです。くどいようですが、メンバ関数には隠し引数である this ポインタがあるのでしたね。これを渡してやる必要があるわけです。

 これには、メンバ関数ポインタを使って関数を呼ぶとき専用の演算子を使います。それが .*->* です。.* はオブジェクトそのまま(または参照など)を使って呼ぶときに、->* はポインタを使って呼ぶときに使います。要は普通にメンバ関数を呼ぶ感じに書いて、* を挟めばいいわけです。

CTest c, *p = &c;
c.*fpFunc();
p->*fpFunc();

 おっと! ここで注意です! .* や ->* は関数呼び出しの ( ) よりも優先順位が低いので、これでは

c.*(fpFunc());
p->*(fpFunc());

と同じ事になり、結局同じエラーが出ることになります。つまり、

CTest c, *p = &c;
(c.*fpFunc)();
(p->*fpFunc)();

と、カッコで囲んでやる必要があるのです。何ともダサい仕様ですが、そうなっているので仕方がありません。

 さらに、void (CTest::*)(); と CTest 用のメンバ関数ポインタだからといって、CTest のメンバ関数内から呼ぶときに this->* が省略できるわけではありません。つまり、

class CTest
{
public:
    void Func();
    void Func2();
};

void CTest::Func()
{
    void (CTest::*fpFunc)() = Func2;
    (this->*fpFunc)();
}

とする必要があるわけです。なんか釈然としませんが、これもそうなっているので仕方がありません。


 長くなるのでこれ以上の話は次回に回したいと思います。では、今回の要点です。


 では、また。


第56章 クロスキャスト | 第58章 メンバ関数ポインタ天国2

Last update was done on 2001.6.9

この講座の著作権はロベールが保有しています