モーダルを閉じる工作HardwareHub ロゴ画像

工作HardwareHubは、ロボット工作や電子工作に関する情報やモノが行き交うコミュニティサイトです。さらに詳しく

利用規約プライバシーポリシー に同意したうえでログインしてください。

工作HardwareHub ロゴ画像 (Laptop端末利用時)
工作HardwareHub ロゴ画像 (Mobile端末利用時)

クラスの基本/継承およびアップキャストと仮想関数 (C++をもう一度)

モーダルを閉じる

ステッカーを選択してください

モーダルを閉じる

お支払い内容をご確認ください

購入商品
」ステッカーの表示権
メッセージ
料金
(税込)
決済方法
GooglePayマーク
決済プラットフォーム
確認事項

利用規約をご確認のうえお支払いください

※カード情報はGoogleアカウント内に保存されます。本サイトやStripeには保存されません

※記事の執筆者は購入者のユーザー名を知ることができます

※購入後のキャンセルはできません

作成日作成日
2014/12/07
最終更新最終更新
2021/10/07
記事区分記事区分
一般公開

目次

    低レイヤーのプログラミングとOS開発が趣味。C言語を使っています。

    0
    ステッカーを贈るとは?

    サンプルコード

    #include <iostream>
    using namespace std;
    
    class MyClass {
    public: // 外部からも派生クラスからも見える
        int Get() const;
    
    private: // 外部からも派生クラスからも見えない
    protected: // 外部からは見えないが派生クラスからは見える
        int m_intval;
    };
    
    class MySubClass :
        public MyClass // 継承
    // アクセス指定子 (省略時はprivateです。大抵の場合publicを選んでおけばよいです):
    // - public: 親のpublic,protected,privateをそのまま引き継ぐ
    // - private: 親のpublic,protectedをprivateにして引き継ぐ
    // - protected: 親のpublicをprotectedにして引き継ぐ
    // ↑要するに厳しい条件を採用して引き継ぎます。ちなみに、protected,privateで継承するとアップキャストを禁止できます。
    {
    public:
        MySubClass(int intval);
    };
    
    int MyClass::Get() const {
        return m_intval;
    }
    
    MySubClass::MySubClass(int intval) {
        m_intval = intval;
    }
    
    int main() {
        MySubClass obj(0);
        cout << obj.Get() << endl;
    
        /* アップキャスト (upcast) */
        /* - 継承木の上 (up) へのキャスト */
        /* - 参照先の実体は派生クラス */
        /* - 参照を用いて実行可能なのは親クラスができることのみ */
        const MyClass& ref = obj; // アップキャストに関してconstは任意
        cout << ref.Get() << endl;
    
        const MyClass* ptr = &obj;
        cout << ptr->Get() << endl;
    
        return 0;
    }
    

    オーバーライド (仮想関数とアップキャスト)

    アップキャストした参照を用いると、派生クラスでオーバーライドされたメンバ関数を実行したつもりであっても基底クラスのものが実行されます。派生クラスのメンバ関数を実行させるためには virtual を基底クラスのメンバ関数に付与しておく必要があります。前者の挙動は意図したものではない場合が多いため「オーバーライドするメンバ関数には基底クラスと派生クラスともに virtual を付与」するのが通常の作法です。

    #include <iostream>
    using namespace std;
    
    class MyClass {
    public:
        virtual void Say(); // 仮想関数
        // virtual void Say() = 0; // 純粋仮想関数 (MyClassは抽象クラスとなり実体を持てなくなる) (*1)
    };
    
    class MySubClass :
        public MyClass
    {
    public:
        // 「必須ではない」が分かりやすさのために
        // virtual を付与するとよい。
        virtual void Say();
    };
    
    // void MyClass::Say() { // ← 使用予定がなければ実体を実装する必要はない (*1)
    //     cout << "MyClass::Say" << endl;
    // }
    
    void MySubClass::Say() {
        cout << "MySubClass::Say" << endl;
    }
    
    int main() {
        // MyClass obj; // (*1)
        MySubClass objSub;
        MyClass& objSubRef = objSub;
    
        // obj.Say();        //=> MyClass::Say // (*1)
        objSub.Say();     //=> MySubClass::Say
        objSubRef.Say();  //=> MySubClass::Say
    
        return 0;
    }
    

    以下のようにアップキャストした参照で直接的に実行する場合でなくても、派生クラスでオーバーライドしたメンバ関数が使用されます。

    #include <iostream>
    using namespace std;
    
    class MyClass {
    public:
        void Say();
    
    protected: // 派生クラスで見える必要はあるが外部から実行予定はない
        virtual void SayBase(); // 仮想関数
    };
    
    class MySubClass :
        public MyClass
    {
    protected:
        virtual void SayBase();
    };
    
    void MyClass::Say() {
        SayBase();
    }
    
    void MyClass::SayBase() {
        cout << "MyClass::SayBase" << endl;
    }
    
    void MySubClass::SayBase() {
        cout << "MySubClass::SayBase" << endl;
    }
    
    int main() {
        MyClass obj;
        MySubClass objSub;
        MyClass& objSubRef = objSub;
    
        obj.Say();        //=> MyClass::SayBase
        objSub.Say();     //=> MySubClass::SayBase
        objSubRef.Say();  //=> MySubClass::SayBase
    
        return 0;
    }
    

    更に、オーバーライドした関数内で、基底クラスの関数を呼び出すこともできます。

    #include <iostream>
    using namespace std;
    
    class MyClass {
    public:
        virtual void Say(); // 仮想関数
    };
    
    class MySubClass :
        public MyClass
    {
    public:
        virtual void Say();
    };
    
    void MyClass::Say() {
        cout << "MyClass::Say" << endl;
    }
    
    void MySubClass::Say() {
        MyClass::Say();
        cout << "MySubClass::Say" << endl;
    }
    
    int main() {
        MySubClass obj;
        obj.Say();  //=> MyClass::Say
                    //   MySubClass::Say
        return 0;
    }
    

    オーバーロードのある仮想関数をオーバーライド

    引数の型に応じてオーバーロードがなされているメンバ仮想関数を派生クラスでオーバーライドする際には、すべてがオーバーライドされてしまいます。これを避けるためには using を使用します。

    #include <iostream>
    using namespace std;
    
    class MyClass {
    public:
        virtual ~MyClass() {}
    public:
        virtual void Show(int intval) {
            cout << "MyClass:Show(" << intval << ")" << endl;
        }
        virtual void Show(char chval) {
            cout << "MyClass:Show(" << chval << ")" << endl;
        }
    };
    
    class MySubClass :
        public MyClass
    {
    public:
        // Show(int) のオーバライド時に Show(char) が消える
        virtual void Show(int intval) {
            cout << "MySubClass:Show(" << intval << ")" << endl;
        }
    
        // 以下の構文で Show(char) を復旧できる
        using MyClass::Show;
    
        // 参考: private と併用して Show(*) を使用禁止にできる
    // private:
    //     using MyClass::Show;
    };
    
    int main() {
        MySubClass obj;
        obj.Show(97); //=> MySubClass:Show(97)
        obj.Show('a'); //=> MyClass:Show(a)
        return 0;
    }
    

    継承における引数のあるコンストラクタ

    継承すると

    • 基底クラスのコンストラクタ → 派生クラスのコンストラクタ
    • 派生クラスのデストラクタ → 基底クラスのデストラクタ

    という順番で処理が行われます。このうち基底クラスのコンストラクタについて、引数がある場合は以下のように明示する必要があることに注意しましょう。

    またこれは非常に重要なことですが、基底クラスのコンストラクタで (純) 仮想関数を実行すると、仮想関数にも関わらず基底クラスのものを実行しようとします。当然のことながら、純仮想関数の場合は実装がないためエラーになります。これは、上記順番の通り派生クラスのコンストラクタが実行前であり、派生クラスでオーバーライドしたものを実行できないためです。同様に、基底クラスのデストラクタで (純) 仮想関数を実行した場合にも、派生クラスはすでにデストラクタで存在がなくなってしまっているため、基底クラスのものを実行しようとします。

    #include <iostream>
    using namespace std;
    
    class MyClass {
    public:
        MyClass(int intval);
    
    private:
        int m_intval;
    };
    
    class MySubClass :
        public MyClass
    {
    public:
        MySubClass(int intval);
    
    private:
        int m_intval_sub;
    };
    
    MyClass::MyClass(int intval) :
        m_intval(intval) // 基本型intにも実はコンストラクタがあり、それを利用して初期化できます。
    {
        // m_intval = intval; // と代入するよりもシンプルです。
    }
    
    MySubClass::MySubClass(int intval) :
        MyClass(intval), // 基底クラスのコンストラクタに引数が不要な場合は省略できます。
        m_intval_sub(intval) // 記載の順序によらず、基底クラスのコンストラクタが優先実行されます。
    {
        // 何らかの処理
    }
    
    int main() {
        MySubClass obj(0);
        return 0;
    }
    
    0
    詳細設定を開く/閉じる
    アカウント プロフィール画像 (本文下)

    低レイヤーのプログラミングとOS開発が趣味。C言語を使っています。

    記事の執筆者にステッカーを贈る

    有益な情報に対するお礼として、またはコメント欄における質問への返答に対するお礼として、 記事の読者は、執筆者に有料のステッカーを贈ることができます。

    さらに詳しく →
    ステッカーを贈る コンセプト画像

    Feedbacks

    Feedbacks コンセプト画像

      ログインするとコメントを投稿できます。

      関連記事