13章 包含と連鎖

http://www.geocities.jp/ky_webid/cpp/language/013.html

なにやら難しそうな単語が出てきました。「ほうがん」と読むらしいです。

要はクラスのメンバに別のクラスを持つということでしょうか。

連鎖というのは単に他のクラスのメンバ関数を呼ぶだけっぽいですね。

とりあえずまずは包含のコンストラクタとデストラクタの挙動を見てみます。

#include <iostream>

class CSuper {
public:
    CSuper ();
    ~CSuper ();
};

class CInner {
public:
    CInner();
    ~CInner();
};

class CSub : public CSuper {
public:
    CSub ();
    ~CSub ();
    CInner m_inner; // 包含
};

CSuper::CSuper () {
    std::cout << "CSuper::CSuper" << std::endl;
}

CSuper::~CSuper () {
    std::cout << "CSuper::~CSuper" << std::endl;
}

CInner::CInner () {
    std::cout << "CInner::CInner" << std::endl;
}

CInner::~CInner () {
    std::cout << "CInner::~CInner" << std::endl;
}

CSub::CSub () {
    std::cout << "CSub::CSub" << std::endl;
}

CSub::~CSub () {
    std::cout << "CSub::~CSub" << std::endl;
}

int main() {
    CSub obj;
    return 0;
}
$ main
CSuper::CSuper
CInner::CInner
CSub::CSub
CSub::~CSub
CInner::~CInner
CSuper::~CSuper

不可解極まりないですね・・・。

CSubをインスタンス化したのに、何故かCSubのコンストラクタよりもCInnerのコンストラクタの方が先に呼ばれています。

これはどういう解釈なんでしょうか。

色々考えていたところ、9章の話を思い出しました。

継承関係にある2つのクラスの、コンストラクタが呼び出される順序を思い出して下さい。スーパークラス→サブクラス の順番でした。つまり、スーパークラス側のコンストラクタが呼び出された時点では、まだサブクラス側はコンストラクタが呼び出されていません。よって、まだメンバ変数の準備ができていない状態なのです。サブクラスがオーバーライドした関数が、サブクラスのメンバ変数をアクセスしていると問題になります。

http://www.geocities.jp/ky_webid/cpp/language/009.html

これでわかりました。もしCInnerよりも先にCSubのコンストラクタが起動してしまったら、CSubのコンストラクタ内でm_innerにアクセスできないということになってしまいます。CSubのコンストラクタが呼び出された時点で、CInnerはインスタンス化が終わっていなければなりません。またCSuperが呼び出された時点ではCInnerを必要としないのでCSuper→CInner→CSubと順番に初期化が行われるわけなんですね。

そしてデストラクタは逆順になるのでCSub→CInner→CSuperとなるわけです。面白いですね。



次は連鎖を試してみます。

#include <iostream>

class CSuper {
public:
    void foo ();
};

class CInner {
public:
    void foo ();
};

class CSub : public CSuper {
public:
    void foo ();
    CInner m_inner; // 包含
};

void CSuper::foo () {
    std::cout << "CSuper::foo" << std::endl;
}

void CInner::foo () {
    std::cout << "CInner::foo" << std::endl;
}
void CSub::foo () {
    std::cout << "CSub::foo" << std::endl;
    // 連鎖
    CSuper::foo();
    m_inner.foo();
}

int main() {
    CSub obj;
    obj.foo();
    return 0;
}
$ main
CSub::foo
CSuper::foo
CInner::foo

連鎖とか大そうな名前が付いてますが、単にメンバ関数を呼んでるだけですね。