読者です 読者をやめる 読者になる 読者になる

メンバ関数ポインタは悪だろうか?

Cの関数ポインタに疲れていると、関数ポインタはまるで悪のような気がしてくるのだけれど、
では、メンバ関数ポインタは悪だろうか、というような事。
STLなどで指定する場合は置いておいて、自分で呼び出す場合、
「仮想関数でいいじゃん?」
ってなことがある。
たとえば、Listenerとして登録しておいて、インターフェイスを呼び出して貰う、
ということもある。
さてさて、
あるオブジェクトから通知を受けたい、という場合、本当にインターフェイスが良いだろうか?
最近、書いた構造で、
「あるイベントが発生した時点で通知を行うオブジェクトがある。このオブジェクトをクラスAが持っているが、他の用途で使うためにAを継承したBもこのオブジェクトを持っている」
さて、
こいつを正しくListenerで解決できるだろうか?
勿論、クラスAのオブジェクトがイベントを起こしたときは、クラスAのレベルで通知が来て欲しいし、
クラスBのオブジェクトがイベントを起こしたときは、クラスBのレベルで通知が来て欲しい。
こういうとき、意外と仮想関数は使いにくい。
クラスAのインターフェイスを仮想にした場合、Bで実装するとAのオブジェクトもそれを呼び出してしまうし、
クラスAのオブジェクトが呼び出したのか、クラスBのオブジェクトが呼び出したのか、という判別がしづらい。
Listener形式にしたところで、そのインターフェイスは仮想なので、結局クラスBに通知が来てしまう。
こまったもんだ。

で、書いてみたのはこんなコード。
codepad

virtual 継承されたListenerInterfaceは仮想関数であるため、
どちらのオブジェクトもPioneerレベルで呼び出してしまうが、
関数ポインタであればきちんと両方の関数を別個として認識して呼び出してくれる。
これはこれで便利だと思うし、呼び出して貰えるインターフェイスとして、

        accessObject_.setCallback(&Person::callback);

        accessObject_.setCallback(&Pioneer::callback);

と書くのは意外と、Listenerや単なる仮想関数になっているよりも解りやすいんじゃないかな?

using namespace std;

class Person;

typedef void (Person::*AccessObjectCallbackFunction)();

class AccessObject;

std::vector<AccessObject*> accessObjectList_;

class AccessObjectListener
{
public:
    virtual void listenerCallback()=0;
};

class AccessObject
{
public:
    AccessObject(Person& owner)
    : owner_(owner)
    , callback_(0)
    , listener_(0)
    {
        accessObjectList_.push_back(this);
    }
    void setCallback(AccessObjectCallbackFunction callback) { callback_ = callback; }
    void setListener(AccessObjectListener* listener) { listener_ = listener; }
    
    void callback() {
        ((&owner_)->*callback_)();
    }
    
    void listenerCallback() {
        listener_->listenerCallback();
    }
    
private:
    Person& owner_;
    AccessObjectCallbackFunction callback_;
    AccessObjectListener* listener_;
};

class Person : virtual public AccessObjectListener
{
public:
    Person() : accessObject_(*this)
    {
        accessObject_.setCallback(&Person::callback);
        accessObject_.setListener(this);
    }
    void callback() {
        printf("Person::callback\n");
    }
    void listenerCallback() {
        printf("Person::listener\n");
    }
private:
    AccessObject accessObject_;
};

class Pioneer : public Person, virtual public AccessObjectListener
{
public:
    Pioneer() : accessObject_(*this)
    {
        accessObject_.setCallback(static_cast<AccessObjectCallbackFunction>(&Pioneer::callback));
        accessObject_.setListener(this);
    }
    void callback() {
        printf("Pioneer::callback\n");
    }
    void listenerCallback() {
        printf("Pioneer::listener\n");
    }

private:
    AccessObject accessObject_;
};

int main()
{
    Pioneer* pioneer = new Pioneer();
    
    printf("MemberFunctionCallback.\n");
    for (std::vector<AccessObject*>::iterator it = accessObjectList_.begin(); it != accessObjectList_.end(); ++it) {
        (*it)->callback();
    }
    printf("ListenerCallback.\n");
    for (std::vector<AccessObject*>::iterator it = accessObjectList_.begin(); it != accessObjectList_.end(); ++it) {
        (*it)->listenerCallback();
    }
    
    delete pioneer;
    
    return 0;
}