ページ 11

stateパターンの状態

Posted: 2010年6月17日(木) 12:59
by dic
stateパターンを利用しようと思ってstateパターンを勉強してるのですが
思ったところの関数が呼ばれないので理解できていないのかなと思ってます
下のを実行すると
Stage1::A
と出力されるんですが
State::ChangeState
も出力されるんではないかなと思っていたのですが、出力されません
これはどういうことか説明してくれる方いませんでしょうか?

また、2つ質問して悪いのですが、私の持っている参考書ではサブクラスがvirtual関数になってます
サブクラスをvirtual関数にする理由などあったら教えていただけると幸いです

#include <iostream>

class    Connection
{
};


class    State
{
protected:
    void    ChangeState( Connection*, State* );
public:
    void    A( Connection* );
    void    B( Connection* );
    void    C( Connection* );
    void    D( Connection* );
};
void    State::ChangeState( Connection *t, State *s )
{
    printf( "State::ChangeState\n" );
}
void    State::A( Connection* )
{
    printf( "State::A\n" );
}

class    Stage1    :    public    State
{
public:
    static    State*    _instance;
    State*    Instance() {
        if( _instance == 0 )
            _instance = new Stage1;
        return _instance;
    }
    virtual    void    A( Connection* );
    virtual    void    B( Connection* );
};
State*    Stage1::_instance = 0;
void    Stage1::A( Connection* t )
{
    ChangeState( t, Stage1::Instance() );
}
void    Stage1::B( Connection* t ) { }

int main()
{
    State    *stage1 = new Stage1;
    Connection    *connection = new Connection;

    stage1->A( connection );

    return 0;
}

Re:stateパターンの状態

Posted: 2010年6月17日(木) 13:22
by シエル
State(基底クラス)のA関数にvirtualを付けて仮想関数にすれば、
printf( "State::ChangeState\n" );だけが表示されますね。

stage1(派生クラス)にvirtualにする意味は無い気がします。。。

まだC++はあんまり理解していないので、間違ってたらすいません。

Re:stateパターンの状態

Posted: 2010年6月17日(木) 13:39
by DVDM
>>dicさん
> 下のを実行すると Stage1::A と出力されるんですが
Stage1::A ではなく State::A が出力されると思ったのですが違いましたでしょうか。

> State::ChangeState
> も出力されるんではないかなと思っていたのですが、出力されません
main 関数にある stage1->A( connection ); では
State::A が呼び出されています。

State クラスの void A( Connection* ); を
virtual void A( Connection* ); と書き直すことで Stage1::A が呼び出されます。
詳しくはオーバーライドで検索してみて下さい。


> サブクラスをvirtual関数にする理由などあったら教えていただけると幸いです
継承する可能性があるのであれば子クラスでも virtual を付けるという感じでしか覚えていませんので
これについてはよくわかりません。

参考になるかは解りませんがこのような記事を見かけました。
https://developer.mozilla.org/ja/C___Po ... f.e3.81.86

Re:stateパターンの状態

Posted: 2010年6月17日(木) 14:21
by dic
>シエルさん
基底クラスをvirtualにしたら確かに
Stage1::A
State::ChangeStage
と表示されますね
なので、私も基底クラス以外をvirtual にしている私の参考書がわからないです
派生クラスをvirtualにする理由ってなんでしょうね・・・

>DVDMさん
>Stage1::A ではなく State::A が出力されると思ったのですが違いましたでしょうか。
あら?
State *stage1 = new Stage1;
Stage1はStage1のオブジェクトだから
stage1->A( connection );
では stage1::Aが呼ばれるものと考えていましたが・・・
確認しようとしたんですが
VC++6だと stage1 の型がデバッカでみれないです;;

stage1->A( connection ); で
State::A( Connection* ); が呼ばれる???


>https://developer.mozilla.org/ja/C___Po ... f.e3.81.86
でも基底クラスをvirtualにしてますね
画像

Re:stateパターンの状態

Posted: 2010年6月17日(木) 15:02
by DVDM
>>dicさん
上記ソースをコピーしてデバッガで追いかけた画像です。

printf( "State::ChangeState\n" );
printf( "State::A\n" );

画面の出力に上記ソースではこの二行しか見当たらなかったので
State::A と表示されるのでは?と思いました。
もし他の所で Stage1::A と出力するように作られているのであれば私の思い違いです。
申し訳ないです;

Re:stateパターンの状態

Posted: 2010年6月17日(木) 17:44
by MNS
>Stage1はStage1のオブジェクトだから
>stage1->A( connection );
>では stage1::Aが呼ばれるものと考えていましたが・・・

このコードの場合、Stage1クラスとStateクラスの両方で関数Aが定義されていますね。
Stage1クラスはStateクラスを継承していますが、

ここで、Stage1のオブジェクトを作って関数Aを呼び出した場合、
(例えば、Stage1* st1 = new Stage1;  st1->A(connection); )
もちろん、Stage1のメンバ関数であるAが呼ばれます。(つまり"State::ChangeState"と表示される)

しかし、Stateのポインタに、Stage1のオブジェクトのポインタを代入した場合、
プログラムは一体どちらの関数Aを呼び出すでしょうか?

これを決定するために、仮想関数テーブルというものが作られます。
具体的には、(仮想)メンバ関数のアドレス情報が配列形式で埋め込まれていて、
この情報に基づいて呼び出す関数を決定します。

C++では、この仮想関数テーブルというのはどのメンバ関数にも作られるわけではなく、
virtualキーワードが付けられた関数にのみ作られます。(高速化のためでしょうかね)
つまり、Stateクラス(の関数A)には仮想関数テーブルは作られていないので、
Stage1クラスがStateクラスを継承していようと、Stage1クラスの関数Aは呼び出されません。

Re:stateパターンの状態

Posted: 2010年6月18日(金) 16:20
by dic
>DVDMさん
すいません、私の最初のソースコードの入力不足でした
printf( "State::ChangeState\n" );
printf( "State::A\n" );
しかないですね
void    Stage1::A( Connection* t )
{
    printf( "Stage1::A\n" ); // << 入力不足
    ChangeState( t, Stage1::Instance() );
}

>MNSさん
ためしに期待していたことを仮想した場合としてない場合でやってみました
---------------------virtual を基底クラスにつける--------------------------
#include <iostream>

class    Interface
{
public:
    virtual    void    A() { printf( "Interface::A\n" ); }
};

class    Next    :    public    Interface
{
public:
    void    A() { printf( "Next::A\n" ); }
};

int main()
{
    Interface    *p = new Next;
    p->A();
    return 0;
}

---------------------virtual を基底クラスにつけない--------------------------
#include <iostream>

class    Interface
{
public:
    void    A() { printf( "Interface::A\n" ); }
};

class    Next    :    public    Interface
{
public:
    void    A() { printf( "Next::A\n" ); }
};

int main()
{
    Interface    *p = new Next;
    p->A();
    return 0;
}
virtual をつけた場合は期待した通り
Next::A
が表示され
virtual をつけない場合は期待してない動作
Interface::A
が表示されました
継承だけが条件ではないようですね
ありがとうございました