[C++] 基底クラスのコンストラクタや関数を呼び出す方法

C++で基底クラスのコンストラクタや関数を呼び出す方法は、派生クラスの設計において重要です。

基底クラスのコンストラクタは、派生クラスのコンストラクタの初期化リストで呼び出します。

例えば、BaseクラスのコンストラクタをDerivedクラスで呼び出すには、Derivedのコンストラクタの初期化リストでBaseのコンストラクタを指定します。

基底クラスの関数を呼び出すには、Base::functionName()のようにスコープ解決演算子を使用します。

これにより、オーバーライドされた関数を明示的に呼び出すことができます。

この記事でわかること
  • 基底クラスのコンストラクタの呼び出し方法
  • 基底クラスの関数の呼び出し方
  • 派生クラスでの基底クラスの利用法
  • 多重継承の際の注意点
  • インターフェースクラスの活用方法

目次から探す

基底クラスのコンストラクタの呼び出し

コンストラクタの初期化リストとは

コンストラクタの初期化リストは、クラスのメンバ変数を初期化するための構文です。

クラスのコンストラクタ内でメンバ変数を初期化する際に、初期化リストを使用することで、より効率的に初期化を行うことができます。

特に、基底クラスのコンストラクタを呼び出す際に重要な役割を果たします。

#include <iostream>
using namespace std;
class Base {
public:
    Base(int value) {
        cout << "基底クラスのコンストラクタ: " << value << endl;
    }
};
class Derived : public Base {
public:
    Derived(int value) : Base(value) { // 初期化リストで基底クラスのコンストラクタを呼び出す
        cout << "派生クラスのコンストラクタ" << endl;
    }
};
int main() {
    Derived obj(10); // 派生クラスのオブジェクトを生成
    return 0;
}
基底クラスのコンストラクタ: 10
派生クラスのコンストラクタ

基底クラスのコンストラクタを呼び出す方法

基底クラスのコンストラクタを呼び出すには、派生クラスのコンストラクタの初期化リストを使用します。

初期化リストに基底クラスのコンストラクタを指定することで、派生クラスのオブジェクトが生成される際に基底クラスのコンストラクタが呼び出されます。

これにより、基底クラスのメンバ変数も正しく初期化されます。

#include <iostream>
using namespace std;
class Base {
public:
    Base() {
        cout << "基底クラスのデフォルトコンストラクタ" << endl;
    }
};
class Derived : public Base {
public:
    Derived() : Base() { // 初期化リストで基底クラスのデフォルトコンストラクタを呼び出す
        cout << "派生クラスのデフォルトコンストラクタ" << endl;
    }
};
int main() {
    Derived obj; // 派生クラスのオブジェクトを生成
    return 0;
}
基底クラスのデフォルトコンストラクタ
派生クラスのデフォルトコンストラクタ

デフォルトコンストラクタと引数付きコンストラクタ

デフォルトコンストラクタは引数を持たないコンストラクタで、オブジェクトを生成する際に自動的に呼び出されます。

一方、引数付きコンストラクタは、オブジェクト生成時に特定の値を渡すことで呼び出されます。

基底クラスのコンストラクタが引数を持つ場合、派生クラスのコンストラクタでその引数を指定する必要があります。

#include <iostream>
using namespace std;
class Base {
public:
    Base(int value) {
        cout << "基底クラスのコンストラクタ: " << value << endl;
    }
};
class Derived : public Base {
public:
    Derived() : Base(42) { // 引数付きコンストラクタを呼び出す
        cout << "派生クラスのデフォルトコンストラクタ" << endl;
    }
};
int main() {
    Derived obj; // 派生クラスのオブジェクトを生成
    return 0;
}
基底クラスのコンストラクタ: 42
派生クラスのデフォルトコンストラクタ

コンストラクタの呼び出し順序

C++では、オブジェクトが生成される際のコンストラクタの呼び出し順序は、基底クラスのコンストラクタが先に呼び出され、その後に派生クラスのコンストラクタが呼び出されます。

この順序は、基底クラスのメンバ変数が派生クラスのコンストラクタ内で使用される可能性があるため、重要です。

#include <iostream>
using namespace std;
class Base {
public:
    Base() {
        cout << "基底クラスのコンストラクタ" << endl;
    }
};
class Derived : public Base {
public:
    Derived() {
        cout << "派生クラスのコンストラクタ" << endl;
    }
};
int main() {
    Derived obj; // 派生クラスのオブジェクトを生成
    return 0;
}
基底クラスのコンストラクタ
派生クラスのコンストラクタ

基底クラスの関数の呼び出し

スコープ解決演算子の使用

スコープ解決演算子::を使用することで、基底クラスの関数を明示的に呼び出すことができます。

これにより、派生クラスでオーバーライドされた関数ではなく、基底クラスの関数を使用することができます。

特に、同名の関数が派生クラスに存在する場合に有効です。

#include <iostream>
using namespace std;
class Base {
public:
    void display() {
        cout << "基底クラスのdisplay関数" << endl;
    }
};
class Derived : public Base {
public:
    void display() { // オーバーライド
        cout << "派生クラスのdisplay関数" << endl;
    }
    void callBaseDisplay() {
        Base::display(); // スコープ解決演算子を使用して基底クラスの関数を呼び出す
    }
};
int main() {
    Derived obj;
    obj.callBaseDisplay(); // 基底クラスの関数を呼び出す
    return 0;
}
基底クラスのdisplay関数

オーバーライドされた関数の呼び出し

派生クラスで基底クラスの関数をオーバーライドした場合、派生クラスのオブジェクトを通じて呼び出されるのはオーバーライドされた関数です。

基底クラスの関数を呼び出すには、スコープ解決演算子を使用する必要があります。

オーバーライドの仕組みを理解することは、ポリモーフィズムを活用する上で重要です。

#include <iostream>
using namespace std;
class Base {
public:
    virtual void show() { // 仮想関数
        cout << "基底クラスのshow関数" << endl;
    }
};
class Derived : public Base {
public:
    void show() override { // オーバーライド
        cout << "派生クラスのshow関数" << endl;
    }
};
int main() {
    Base* basePtr = new Derived(); // 基底クラスのポインタで派生クラスを指す
    basePtr->show(); // オーバーライドされた関数が呼び出される
    delete basePtr; // メモリ解放
    return 0;
}
派生クラスのshow関数

仮想関数と基底クラスの関数呼び出し

仮想関数は、基底クラスで定義され、派生クラスでオーバーライドされる関数です。

仮想関数を使用することで、基底クラスのポインタや参照を通じて、派生クラスの関数を呼び出すことができます。

これにより、動的ポリモーフィズムが実現されます。

基底クラスの仮想関数を呼び出す場合、オーバーライドされた関数が優先されます。

#include <iostream>
using namespace std;
class Base {
public:
    virtual void print() { // 仮想関数
        cout << "基底クラスのprint関数" << endl;
    }
};
class Derived : public Base {
public:
    void print() override { // オーバーライド
        cout << "派生クラスのprint関数" << endl;
    }
};
int main() {
    Base* basePtr = new Derived(); // 基底クラスのポインタで派生クラスを指す
    basePtr->print(); // 派生クラスのprint関数が呼び出される
    delete basePtr; // メモリ解放
    return 0;
}
派生クラスのprint関数

基底クラスの非仮想関数の呼び出し

基底クラスの非仮想関数は、オーバーライドされることがないため、派生クラスから直接呼び出すことができます。

非仮想関数は、基底クラスのオブジェクトを通じて呼び出される場合、常に基底クラスの実装が使用されます。

これにより、基底クラスの特定の機能を確実に利用することができます。

#include <iostream>
using namespace std;
class Base {
public:
    void display() { // 非仮想関数
        cout << "基底クラスの非仮想関数display" << endl;
    }
};
class Derived : public Base {
public:
    void show() {
        display(); // 基底クラスの非仮想関数を呼び出す
    }
};
int main() {
    Derived obj;
    obj.show(); // 基底クラスの非仮想関数が呼び出される
    return 0;
}
基底クラスの非仮想関数display

派生クラスにおける基底クラスの利用

基底クラスのメンバ変数の初期化

派生クラスのコンストラクタでは、基底クラスのメンバ変数を初期化することができます。

基底クラスのコンストラクタを初期化リストで呼び出すことで、基底クラスのメンバ変数を適切に初期化することが可能です。

これにより、派生クラスのオブジェクトが生成される際に、基底クラスの状態を正しく設定できます。

#include <iostream>
using namespace std;
class Base {
protected:
    int value;
public:
    Base(int v) : value(v) { // 基底クラスのコンストラクタ
        cout << "基底クラスのコンストラクタ: " << value << endl;
    }
};
class Derived : public Base {
public:
    Derived(int v) : Base(v) { // 基底クラスのコンストラクタを呼び出す
        cout << "派生クラスのコンストラクタ" << endl;
    }
};
int main() {
    Derived obj(10); // 派生クラスのオブジェクトを生成
    return 0;
}
基底クラスのコンストラクタ: 10
派生クラスのコンストラクタ

基底クラスのメンバ関数の再利用

派生クラスでは、基底クラスで定義されたメンバ関数を再利用することができます。

これにより、コードの重複を避け、基底クラスの機能を活用することができます。

基底クラスのメンバ関数は、派生クラスのオブジェクトから直接呼び出すことができます。

#include <iostream>
using namespace std;
class Base {
public:
    void display() { // 基底クラスのメンバ関数
        cout << "基底クラスのdisplay関数" << endl;
    }
};
class Derived : public Base {
public:
    void show() { // 基底クラスのメンバ関数を再利用
        display(); // 基底クラスのdisplay関数を呼び出す
    }
};
int main() {
    Derived obj;
    obj.show(); // 基底クラスのメンバ関数を呼び出す
    return 0;
}
基底クラスのdisplay関数

基底クラスのアクセス指定子とその影響

基底クラスのメンバ変数やメンバ関数には、アクセス指定子publicprotectedprivateが設定されています。

これにより、派生クラスからのアクセスが制限されることがあります。

publicメンバは派生クラスから直接アクセス可能ですが、privateメンバはアクセスできません。

protectedメンバは派生クラスからアクセス可能ですが、外部からはアクセスできません。

#include <iostream>
using namespace std;
class Base {
private:
    int privateValue; // privateメンバ
protected:
    int protectedValue; // protectedメンバ
public:
    Base(int pv, int rv) : privateValue(pv), protectedValue(rv) {}
    void show() {
        cout << "protectedValue: " << protectedValue << endl;
    }
};
class Derived : public Base {
public:
    Derived(int pv, int rv) : Base(pv, rv) {}
    void display() {
        // cout << "privateValue: " << privateValue << endl; // エラー: privateメンバにはアクセスできない
        show(); // protectedメンバにはアクセス可能
    }
};
int main() {
    Derived obj(10, 20);
    obj.display(); // protectedメンバを表示
    return 0;
}
protectedValue: 20

応用例

多重継承における基底クラスのコンストラクタ呼び出し

C++では、多重継承を使用して複数の基底クラスから派生クラスを作成することができます。

この場合、各基底クラスのコンストラクタを適切に呼び出す必要があります。

派生クラスのコンストラクタの初期化リストを使用して、すべての基底クラスのコンストラクタを呼び出すことができます。

#include <iostream>
using namespace std;
class Base1 {
public:
    Base1() {
        cout << "Base1のコンストラクタ" << endl;
    }
};
class Base2 {
public:
    Base2() {
        cout << "Base2のコンストラクタ" << endl;
    }
};
class Derived : public Base1, public Base2 {
public:
    Derived() : Base1(), Base2() { // 各基底クラスのコンストラクタを呼び出す
        cout << "Derivedのコンストラクタ" << endl;
    }
};
int main() {
    Derived obj; // 派生クラスのオブジェクトを生成
    return 0;
}
Base1のコンストラクタ
Base2のコンストラクタ
Derivedのコンストラクタ

テンプレートクラスでの基底クラスの利用

C++のテンプレートを使用することで、基底クラスを持つテンプレートクラスを作成することができます。

これにより、異なる型のデータを扱うクラスを柔軟に設計することが可能です。

基底クラスの機能をテンプレートクラスで再利用することができます。

#include <iostream>
using namespace std;
template <typename T>
class Base {
protected:
    T value;
public:
    Base(T v) : value(v) {
        cout << "Baseのコンストラクタ: " << value << endl;
    }
};
template <typename T>
class Derived : public Base<T> {
public:
    Derived(T v) : Base<T>(v) { // 基底クラスのコンストラクタを呼び出す
        cout << "Derivedのコンストラクタ" << endl;
    }
};
int main() {
    Derived<int> obj(100); // int型の派生クラスのオブジェクトを生成
    return 0;
}
Baseのコンストラクタ: 100
Derivedのコンストラクタ

インターフェースクラスとしての基底クラスの活用

基底クラスをインターフェースとして使用することで、異なる派生クラスに共通のメソッドを定義することができます。

これにより、異なるクラス間での一貫性を保ちながら、ポリモーフィズムを活用することができます。

基底クラスは純粋仮想関数を持つことで、インターフェースとして機能します。

#include <iostream>
using namespace std;
class IShape { // インターフェースクラス
public:
    virtual void draw() = 0; // 純粋仮想関数
};
class Circle : public IShape {
public:
    void draw() override { // オーバーライド
        cout << "円を描画" << endl;
    }
};
class Square : public IShape {
public:
    void draw() override { // オーバーライド
        cout << "四角を描画" << endl;
    }
};
int main() {
    IShape* shape1 = new Circle(); // Circleオブジェクト
    IShape* shape2 = new Square(); // Squareオブジェクト
    shape1->draw(); // 円を描画
    shape2->draw(); // 四角を描画
    delete shape1; // メモリ解放
    delete shape2; // メモリ解放
    return 0;
}
円を描画
四角を描画

よくある質問

基底クラスのコンストラクタを呼び出さないとどうなる?

基底クラスのコンストラクタを呼び出さない場合、基底クラスのメンバ変数は初期化されず、未定義の状態になります。

これにより、プログラムの動作が不安定になったり、予期しない結果を引き起こす可能性があります。

特に、基底クラスに重要な初期化処理が含まれている場合、その処理が行われないため、エラーやクラッシュの原因となることがあります。

基底クラスの関数を呼び出す際の注意点は?

基底クラスの関数を呼び出す際には、以下の点に注意が必要です。

  • オーバーライドの影響: 派生クラスで同名の関数がオーバーライドされている場合、基底クラスの関数を呼び出すにはスコープ解決演算子を使用する必要があります。
  • アクセス指定子: 基底クラスのメンバ関数がprivateの場合、派生クラスからはアクセスできません。

protectedpublicであればアクセス可能です。

  • 仮想関数の動作: 基底クラスの仮想関数を呼び出すと、派生クラスでオーバーライドされた関数が呼び出されるため、意図した動作を確認する必要があります。

派生クラスで基底クラスの関数をオーバーライドする必要がある?

派生クラスで基底クラスの関数をオーバーライドする必要はありません。

オーバーライドは、基底クラスの機能を変更したい場合や、特定の動作を実装したい場合に行います。

オーバーライドしない場合、基底クラスの実装がそのまま使用されます。

ただし、オーバーライドを行うことで、ポリモーフィズムを活用し、より柔軟なプログラム設計が可能になります。

まとめ

この記事では、C++における基底クラスのコンストラクタや関数の呼び出し方法について詳しく解説しました。

基底クラスのコンストラクタを適切に呼び出すことや、派生クラスでの基底クラスのメンバ変数や関数の利用方法を理解することで、より効果的なオブジェクト指向プログラミングが可能になります。

これを機に、実際のプログラムに基底クラスの活用を取り入れて、コードの再利用性や可読性を向上させてみてください。

  • URLをコピーしました!
目次から探す