クラスの基本/継承およびアップキャストと仮想関数 (C++をもう一度)
[最終更新] (2019/06/03 00:51:02)
最近の投稿
注目の記事

サンプルコード

#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;
}

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

この続きが気になる方は

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

残り文字数は全体の約 19 %
tybot
100 円
関連ページ
    サンプルコード #include <iostream> using namespace std; class MyClass { public: virtual ~MyClass(); // 仮想デストラクタ public: virtual void Say() = 0; }; class MySubClass : public MyClass { public:
    概要 Scala のトレイトは実装のある Java インターフェースのようなものです。トレイトはクラスにミックスインして使用します。ミックスインは継承のようなものです。コード上ではクラス定義時に extends や with でトレイトを指定します。Scala は Java と同様に親クラスは一つまでしか指定できません。一方でトレイトは Java インターフェースのようにいくつでもミックスインで
    基本型と参照型 Java のデータ型は基本型と参照型に分類されます。基本型には null は代入できません。基本型には null 以外の初期値があり、boolean は false、int や long は 0 です。 class Main { public static void main(String args[]) { // 基本型 boolean
    概要 Scala は JVM 上で動作するバイトコードにコンパイルできる言語です。JAVA よりも柔軟な記述ができます。事前にこちらからダウンロードおよびインストールしておいてください。基本的な文法をまとめます。 変数および定数 詳細は『Scala の型に関する知識』をご参照ください。 object HelloWorld { def main(args: Array[String]):
    概要 C++にはJavaなどと異なりインタフェースという機能が存在しません。C++では純粋仮想関数と仮想デストラクタしかメンバをもたないクラスをインタフェースとして利用します。その際、多重継承や仮想継承の知識が必要になります。 多重継承 #include <iostream> using namespace std; class MyClass { public: virtual ~
    サンプルコード my_class.h #ifndef MY_CLASS_H_ #define MY_CLASS_H_ // 関数テンプレート https://www.qoosky.io/techs/22b50b7062 と同様、 // テンプレートは通常ヘッダファイルにすべてを記述する必要があります。 // ヘッダファイルでの using 使用は好ましくないため std::cout 等とします
    基本的な構文 #include <iostream> #include <cstdlib> using namespace std; void MyFunc() { if(true) { throw "MyFunc"; } // 正常時の処理... } int main() { try { if (true) { // エラー判
    概要 C++ を Python から利用する方法の一つに pybind11 があります。C++11 をサポートするコンパイラが必要です。サンプルコードを記載します。 pybind11 — Seamless operability between C++11 and Python Reference 簡単なサンプル