先頭へ戻る

仮想関数 解答ページ | Programming Place Plus C++編【言語解説】 第27章

Programming Place Plus トップページC++編

先頭へ戻る

C++編で扱っている C++ は 2003年に登場した C++03 という、とても古いバージョンのものです。C++ はその後、C++11 -> C++14 -> C++17 -> C++20 と更新されており、今後も 3年ごとに更新されます。
なかでも C++11 での更新は非常に大きなものであり、これから C++ の学習を始めるのなら、C++11 よりも古いバージョンを対象にするべきではありません。特に事情がないなら、新しい C++ を学んでください。 当サイトでは、C++14 をベースにした新C++編を作成中です。

問題①

問題① 仮想関数が定義されることで、オブジェクトの大きさが大きくなることを確認してください。


簡単なプログラムを書いて確かめてみましょう。

#include <iostream>

class C1 {
public:
    void f1() {}
    void f2() {}
    void f3() {}
};

class C2 {
public:
    virtual void f1() {}
    void f2() {}
    void f3() {}
};

class C3 {
public:
    virtual void f1() {}
    virtual void f2() {}
    virtual void f3() {}
};

class C4 : public C3 {
public:
    void f4() {}
    void f5() {}
    void f6() {}
};

class C5 : public C3 {
public:
    virtual void f4() {}
    virtual void f5() {}
    virtual void f6() {}
};

class C6 : public C3 {
public:
    virtual void f4() {}
    virtual void f5() {}
    virtual void f6() {}
    virtual void f7() {}
    virtual void f8() {}
    virtual void f9() {}
    virtual void f10() {}
    virtual void f11() {}
};



int main()
{
    C1 c1;
    C2 c2;
    C3 c3;
    C4 c4;
    C5 c5;
    C6 c6;

    std::cout << sizeof(c1) << "\n"
              << sizeof(c2) << "\n"
              << sizeof(c3) << "\n"
              << sizeof(c4) << "\n"
              << sizeof(c5) << "\n"
              << sizeof(c6) << std::endl;
}

実行結果:

1
4
4
4
4
4

C4クラスには仮想関数がありませんが、継承元の C3クラスには仮想関数があるので、仮想関数を含んでいないクラスは C1 だけです。結果、C1クラスだけが 1バイト、他はすべて 4バイトになりました。

このように、仮想関数が定義されていると、オブジェクト1つごとに、ポインタ1個分多く大きさを取ります。

【上級】ちなみに C1クラスが、メンバ変数がなくても 1バイト使っているのは、C++ の仕様です。空であっても、そのクラスのオブジェクトを指すポインタを作れるように、1バイトの実体は確保されます。なお、メンバ変数がないクラスを継承した場合は、コンパイラの最適化によって、その 1バイトも削られる可能性があります。

問題②

問題② コンストラクタやデストラクタを「限定公開」にすることには、どのような意味がありますか?


コンストラクタが「限定公開」になっていると、そのクラス自身か、派生クラスからしかインスタンス化できないことになります。これは、継承することが必須であることを意味します。(そのクラス自身でのインスタンス化しかできないようにすることが目的なら、コンストラクタを「非公開」にするべきです)。

デストラクタを「限定公開」にすることも、結局のところ同様で、継承することが必須であることを意味しています。この場合、基底クラスの型のオブジェクトを直接解放することができず、必ず派生クラス経由でデストラクタが呼び出されることになります。そのため、デストラクタを仮想にする必要がないことも注目すべき点です。

問題③

問題③ 以下のプログラムがしていることを説明してください。Pen と DotPen は、この章のサンプルプログラムと同じものとします。

int main()
{
    typedef std::vector<Pen*> PenContainer;

    PenContainer pens;
    pens.push_back(new Pen(0xffffff));
    pens.push_back(new DotPen(0x000000));
    pens.push_back(new DotPen(0xff0000));
    pens.push_back(new Pen(0x00ff00));
    pens.push_back(new Pen(0x0000ff));

    int x1 = 0;
    int y1 = 0;
    int x2 = 0;
    int y2 = 100;

    const PenContainer::iterator itEnd = pens.end();
    for (PenContainer::iterator it = pens.begin(); it != itEnd; ++it) {
        (*it)->DrawLine(x1, y1, x2, y2);
        x1 += 20;
        x2 += 20;
    }
}

公開継承の関係にあるクラスであれば、基底クラス型のデータ構造に、異なる型のオブジェクトを混在して格納できます。この問題の場合、vector の要素は、基底クラスである Pen のポインタ型ですから、Pen のポインタも DotPen のポインタも格納できます。

この vector をイテレータを使って走査し、それぞれの要素の DrawLineメンバ関数を呼び出しています。イテレータが指す先は Pen* であり、これを経由した呼び出しなので、多態性の効力が発揮されます。結果、オブジェクトの正体が Pen なら Pen の関数が、DotPen なら DotPen の関数が選択され、呼び分けられます。

変数x1、x2、y1、y2 はそれほど深い意味はありませんが、縦にまっすぐな直線を、右へ右へとずらしながら描画しています。


参考リンク

更新履歴

’2018/2/22 「サイズ」という表記について表現を統一。 型のサイズ(バイト数)を表しているところは「大きさ」、要素数を表しているところは「要素数」。

’2018/2/21 文章中の表記を統一(bit、Byte -> ビット、バイト)

’2016/3/28 新規作成。



第27章のメインページへ

C++編のトップページへ

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
Twitter でツイート Twitter をフォロー LINE で送る
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー