[C++] 継承先クラスで基底クラスのコンストラクタを呼び出す方法

C++では、継承先クラス(派生クラス)のコンストラクタで基底クラスのコンストラクタを呼び出すには、派生クラスのコンストラクタの初期化リストで基底クラスのコンストラクタを指定します。

具体的には、派生クラスのコンストラクタの定義で「: 基底クラス名(引数)」の形式を使用します。

これにより、基底クラスのコンストラクタが派生クラスのコンストラクタの実行前に呼び出されます。

この記事でわかること
  • 基底クラスのコンストラクタ呼び出しの方法
  • 引数付きコンストラクタの使い方
  • 多重継承における注意点
  • 仮想継承のコンストラクタ呼び出し
  • コンストラクタ呼び出しのエラー対処法

目次から探す

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

C++において、継承を使用する際には、基底クラスのコンストラクタを適切に呼び出すことが重要です。

これにより、基底クラスのメンバ変数が正しく初期化され、派生クラスが期待通りに動作します。

以下では、基底クラスのコンストラクタを呼び出す方法について詳しく解説します。

初期化リストの基本

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

初期化リストは、コンストラクタの引数リストの後にコロン(:)を付けて記述します。

以下はその基本的な構文です。

#include <iostream>
class Base {
public:
    Base() {
        std::cout << "Baseのコンストラクタが呼ばれました。" << std::endl;
    }
};
class Derived : public Base {
public:
    Derived() : Base() { // 初期化リストで基底クラスのコンストラクタを呼び出す
        std::cout << "Derivedのコンストラクタが呼ばれました。" << std::endl;
    }
};
int main() {
    Derived d; // Derivedのインスタンスを作成
    return 0;
}
Baseのコンストラクタが呼ばれました。
Derivedのコンストラクタが呼ばれました。

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

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

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

以下の例では、基底クラスのコンストラクタを呼び出す方法を示します。

#include <iostream>
class Base {
public:
    Base() {
        std::cout << "Baseのコンストラクタが呼ばれました。" << std::endl;
    }
};
class Derived : public Base {
public:
    Derived() : Base() { // 基底クラスのコンストラクタを呼び出す
        std::cout << "Derivedのコンストラクタが呼ばれました。" << std::endl;
    }
};
int main() {
    Derived d; // Derivedのインスタンスを作成
    return 0;
}
Baseのコンストラクタが呼ばれました。
Derivedのコンストラクタが呼ばれました。

引数付きコンストラクタの呼び出し

基底クラスに引数付きのコンストラクタがある場合、派生クラスのコンストラクタからその引数を渡す必要があります。

以下の例では、引数を持つ基底クラスのコンストラクタを呼び出す方法を示します。

#include <iostream>
class Base {
public:
    Base(int value) {
        std::cout << "Baseのコンストラクタが呼ばれました。値: " << value << std::endl;
    }
};
class Derived : public Base {
public:
    Derived(int value) : Base(value) { // 引数を基底クラスに渡す
        std::cout << "Derivedのコンストラクタが呼ばれました。" << std::endl;
    }
};
int main() {
    Derived d(10); // 引数を渡してDerivedのインスタンスを作成
    return 0;
}
Baseのコンストラクタが呼ばれました。値: 10
Derivedのコンストラクタが呼ばれました。

デフォルトコンストラクタの呼び出し

基底クラスにデフォルトコンストラクタがある場合、派生クラスのコンストラクタで明示的に呼び出す必要はありません。

C++は自動的に基底クラスのデフォルトコンストラクタを呼び出します。

以下の例を見てみましょう。

#include <iostream>
class Base {
public:
    Base() {
        std::cout << "Baseのデフォルトコンストラクタが呼ばれました。" << std::endl;
    }
};
class Derived : public Base {
public:
    Derived() { // 基底クラスのデフォルトコンストラクタは自動的に呼ばれる
        std::cout << "Derivedのコンストラクタが呼ばれました。" << std::endl;
    }
};
int main() {
    Derived d; // Derivedのインスタンスを作成
    return 0;
}
Baseのデフォルトコンストラクタが呼ばれました。
Derivedのコンストラクタが呼ばれました。

明示的に基底クラスのコンストラクタを呼び出す必要がある場合

基底クラスのコンストラクタが引数を持つ場合や、特定の初期化が必要な場合には、派生クラスのコンストラクタで明示的に基底クラスのコンストラクタを呼び出す必要があります。

以下の例では、基底クラスの引数付きコンストラクタを明示的に呼び出しています。

#include <iostream>
class Base {
public:
    Base(int value) {
        std::cout << "Baseのコンストラクタが呼ばれました。値: " << value << std::endl;
    }
};
class Derived : public Base {
public:
    Derived(int value) : Base(value) { // 明示的に基底クラスのコンストラクタを呼び出す
        std::cout << "Derivedのコンストラクタが呼ばれました。" << std::endl;
    }
};
int main() {
    Derived d(20); // 引数を渡してDerivedのインスタンスを作成
    return 0;
}
Baseのコンストラクタが呼ばれました。値: 20
Derivedのコンストラクタが呼ばれました。

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

基底クラスのコンストラクタを呼び出す方法は、単純な継承だけでなく、さまざまな状況で応用されます。

ここでは、複数の基底クラスを持つ場合や仮想継承、テンプレートクラス、さらに派生クラスでのメンバ初期化について解説します。

複数の基底クラスを持つ場合(多重継承)

C++では、多重継承を使用して複数の基底クラスを持つことができます。

この場合、各基底クラスのコンストラクタを初期化リストで呼び出す必要があります。

以下の例では、2つの基底クラスを持つ派生クラスのコンストラクタを示します。

#include <iostream>
class Base1 {
public:
    Base1() {
        std::cout << "Base1のコンストラクタが呼ばれました。" << std::endl;
    }
};
class Base2 {
public:
    Base2() {
        std::cout << "Base2のコンストラクタが呼ばれました。" << std::endl;
    }
};
class Derived : public Base1, public Base2 {
public:
    Derived() : Base1(), Base2() { // 各基底クラスのコンストラクタを呼び出す
        std::cout << "Derivedのコンストラクタが呼ばれました。" << std::endl;
    }
};
int main() {
    Derived d; // Derivedのインスタンスを作成
    return 0;
}
Base1のコンストラクタが呼ばれました。
Base2のコンストラクタが呼ばれました。
Derivedのコンストラクタが呼ばれました。

仮想継承とコンストラクタの呼び出し

仮想継承を使用する場合、基底クラスのコンストラクタは派生クラスの最上位のコンストラクタで呼び出されます。

これにより、基底クラスのインスタンスが一度だけ作成されることが保証されます。

以下の例では、仮想継承を使用した場合のコンストラクタ呼び出しを示します。

#include <iostream>
class Base {
public:
    Base() {
        std::cout << "Baseのコンストラクタが呼ばれました。" << std::endl;
    }
};
class Derived1 : virtual public Base {
public:
    Derived1() {
        std::cout << "Derived1のコンストラクタが呼ばれました。" << std::endl;
    }
};
class Derived2 : virtual public Base {
public:
    Derived2() {
        std::cout << "Derived2のコンストラクタが呼ばれました。" << std::endl;
    }
};
class FinalDerived : public Derived1, public Derived2 {
public:
    FinalDerived() {
        std::cout << "FinalDerivedのコンストラクタが呼ばれました。" << std::endl;
    }
};
int main() {
    FinalDerived fd; // FinalDerivedのインスタンスを作成
    return 0;
}
Baseのコンストラクタが呼ばれました。
Derived1のコンストラクタが呼ばれました。
Derived2のコンストラクタが呼ばれました。
FinalDerivedのコンストラクタが呼ばれました。

テンプレートクラスと継承におけるコンストラクタの呼び出し

テンプレートクラスを使用する場合でも、基底クラスのコンストラクタを呼び出すことができます。

テンプレートパラメータを使用して、基底クラスのコンストラクタに引数を渡すことが可能です。

以下の例では、テンプレートクラスの継承におけるコンストラクタ呼び出しを示します。

#include <iostream>
template <typename T>
class Base {
public:
    Base(T value) {
        std::cout << "Baseのコンストラクタが呼ばれました。値: " << value << std::endl;
    }
};
template <typename T>
class Derived : public Base<T> {
public:
    Derived(T value) : Base<T>(value) { // 基底クラスのコンストラクタを呼び出す
        std::cout << "Derivedのコンストラクタが呼ばれました。" << std::endl;
    }
};
int main() {
    Derived<int> d(42); // 引数を渡してDerivedのインスタンスを作成
    return 0;
}
Baseのコンストラクタが呼ばれました。値: 42
Derivedのコンストラクタが呼ばれました。

派生クラスで基底クラスのメンバを初期化する方法

派生クラスのコンストラクタで基底クラスのメンバを初期化するには、初期化リストを使用して基底クラスのコンストラクタを呼び出すことが重要です。

以下の例では、基底クラスのメンバを初期化する方法を示します。

#include <iostream>
class Base {
public:
    int value;
    Base(int v) : value(v) { // メンバを初期化
        std::cout << "Baseのコンストラクタが呼ばれました。値: " << value << std::endl;
    }
};
class Derived : public Base {
public:
    Derived(int v) : Base(v) { // 基底クラスのコンストラクタを呼び出す
        std::cout << "Derivedのコンストラクタが呼ばれました。" << std::endl;
    }
};
int main() {
    Derived d(100); // 引数を渡してDerivedのインスタンスを作成
    return 0;
}
Baseのコンストラクタが呼ばれました。値: 100
Derivedのコンストラクタが呼ばれました。

基底クラスのコンストラクタ呼び出しに関する注意点

基底クラスのコンストラクタを呼び出す際には、いくつかの注意点があります。

これらの注意点を理解しておくことで、プログラムのエラーを未然に防ぎ、正しく動作するコードを書くことができます。

以下では、基底クラスのコンストラクタに関する重要な注意点を解説します。

基底クラスのコンストラクタが呼ばれない場合のエラー

基底クラスのコンストラクタが呼ばれない場合、基底クラスのメンバが初期化されず、未定義の動作を引き起こす可能性があります。

特に、基底クラスのコンストラクタがデフォルトでない場合、派生クラスのコンストラクタで基底クラスのコンストラクタを明示的に呼び出す必要があります。

以下の例では、基底クラスのコンストラクタが呼ばれない場合のエラーを示します。

#include <iostream>
class Base {
public:
    Base(int value) {
        std::cout << "Baseのコンストラクタが呼ばれました。値: " << value << std::endl;
    }
};
class Derived : public Base {
public:
    // Baseのコンストラクタを呼び出さないとエラーになる
    Derived() { 
        std::cout << "Derivedのコンストラクタが呼ばれました。" << std::endl;
    }
};
int main() {
    Derived d; // エラー: Baseのコンストラクタが呼ばれない
    return 0;
}
エラー: 基底クラス 'Base' のコンストラクタが呼ばれません。

基底クラスのコンストラクタがprivateの場合

基底クラスのコンストラクタがprivateの場合、派生クラスからそのコンストラクタを呼び出すことはできません。

この場合、派生クラスのインスタンスを作成することができず、コンパイルエラーが発生します。

以下の例では、firendを用いてDerivedクラスからのみprivateな基底クラスのコンストラクタを呼び出せるようにしたものです。

#include <iostream>
class Base {
private:
    Base() { // privateなコンストラクタ
        std::cout << "Baseのコンストラクタが呼ばれました。" << std::endl;
    }
    friend class Derived; // Derivedクラスからアクセスを許可
};
class Derived : public Base {
public:
    Derived() { // Baseのコンストラクタを呼び出すことができる
        std::cout << "Derivedのコンストラクタが呼ばれました。" << std::endl;
    }
};
int main() {
    Derived d; // Derivedのインスタンスを作成
    return 0;
}
Baseのコンストラクタが呼ばれました。
Derivedのコンストラクタが呼ばれました。

基底クラスのコンストラクタがexplicitの場合

基底クラスのコンストラクタがexplicitとして宣言されている場合、暗黙の型変換が行われず、明示的に引数を渡す必要があります。

これにより、コンストラクタの呼び出しが明確になり、意図しない動作を防ぐことができます。

以下の例では、explicitな基底クラスのコンストラクタを示します。

#include <iostream>
class Base {
public:
    explicit Base(int value) { // explicitなコンストラクタ
        std::cout << "Baseのコンストラクタが呼ばれました。値: " << value << std::endl;
    }
};
class Derived : public Base {
public:
    Derived(int value) : Base(value) { // 明示的に引数を渡す
        std::cout << "Derivedのコンストラクタが呼ばれました。" << std::endl;
    }
};
int main() {
    Derived d(30); // 引数を渡してDerivedのインスタンスを作成
    return 0;
}
Baseのコンストラクタが呼ばれました。値: 30
Derivedのコンストラクタが呼ばれました。

基底クラスのコンストラクタが例外を投げる場合

基底クラスのコンストラクタが例外を投げる場合、派生クラスのインスタンスの生成は失敗します。

この場合、例外処理を行うことで、プログラムの安定性を保つことが重要です。

以下の例では、基底クラスのコンストラクタが例外を投げる場合を示します。

#include <iostream>
#include <stdexcept>
class Base {
public:
    Base() {
        throw std::runtime_error("Baseのコンストラクタでエラーが発生しました。"); // 例外を投げる
    }
};
class Derived : public Base {
public:
    Derived() {
        std::cout << "Derivedのコンストラクタが呼ばれました。" << std::endl;
    }
};
int main() {
    try {
        Derived d; // Derivedのインスタンスを作成
    } catch (const std::runtime_error& e) {
        std::cout << "例外キャッチ: " << e.what() << std::endl; // 例外をキャッチ
    }
    return 0;
}
例外キャッチ: Baseのコンストラクタでエラーが発生しました。

実際のコード例

ここでは、C++における継承とコンストラクタ呼び出しの実際のコード例をいくつか示します。

これにより、基底クラスのコンストラクタを呼び出す方法を具体的に理解することができます。

基本的な継承とコンストラクタ呼び出しの例

基本的な継承の例では、基底クラスのデフォルトコンストラクタを呼び出す方法を示します。

以下のコードでは、基底クラスのコンストラクタが派生クラスのコンストラクタから自動的に呼び出されます。

#include <iostream>
class Base {
public:
    Base() {
        std::cout << "Baseのコンストラクタが呼ばれました。" << std::endl;
    }
};
class Derived : public Base {
public:
    Derived() { // 基底クラスのコンストラクタは自動的に呼ばれる
        std::cout << "Derivedのコンストラクタが呼ばれました。" << std::endl;
    }
};
int main() {
    Derived d; // Derivedのインスタンスを作成
    return 0;
}
Baseのコンストラクタが呼ばれました。
Derivedのコンストラクタが呼ばれました。

引数付きコンストラクタの呼び出し例

引数付きの基底クラスのコンストラクタを呼び出す例では、派生クラスのコンストラクタから引数を渡す方法を示します。

以下のコードでは、基底クラスのコンストラクタに引数を渡しています。

#include <iostream>
class Base {
public:
    Base(int value) {
        std::cout << "Baseのコンストラクタが呼ばれました。値: " << value << std::endl;
    }
};
class Derived : public Base {
public:
    Derived(int value) : Base(value) { // 引数を基底クラスに渡す
        std::cout << "Derivedのコンストラクタが呼ばれました。" << std::endl;
    }
};
int main() {
    Derived d(42); // 引数を渡してDerivedのインスタンスを作成
    return 0;
}
Baseのコンストラクタが呼ばれました。値: 42
Derivedのコンストラクタが呼ばれました。

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

多重継承を使用する場合、複数の基底クラスのコンストラクタを呼び出す必要があります。

以下のコードでは、2つの基底クラスを持つ派生クラスのコンストラクタを示します。

#include <iostream>
class Base1 {
public:
    Base1() {
        std::cout << "Base1のコンストラクタが呼ばれました。" << std::endl;
    }
};
class Base2 {
public:
    Base2() {
        std::cout << "Base2のコンストラクタが呼ばれました。" << std::endl;
    }
};
class Derived : public Base1, public Base2 {
public:
    Derived() : Base1(), Base2() { // 各基底クラスのコンストラクタを呼び出す
        std::cout << "Derivedのコンストラクタが呼ばれました。" << std::endl;
    }
};
int main() {
    Derived d; // Derivedのインスタンスを作成
    return 0;
}
Base1のコンストラクタが呼ばれました。
Base2のコンストラクタが呼ばれました。
Derivedのコンストラクタが呼ばれました。

仮想継承におけるコンストラクタ呼び出しの例

仮想継承を使用する場合、基底クラスのコンストラクタは最上位の派生クラスのコンストラクタで呼び出されます。

以下のコードでは、仮想継承を使用した場合のコンストラクタ呼び出しを示します。

#include <iostream>
class Base {
public:
    Base() {
        std::cout << "Baseのコンストラクタが呼ばれました。" << std::endl;
    }
};
class Derived1 : virtual public Base {
public:
    Derived1() {
        std::cout << "Derived1のコンストラクタが呼ばれました。" << std::endl;
    }
};
class Derived2 : virtual public Base {
public:
    Derived2() {
        std::cout << "Derived2のコンストラクタが呼ばれました。" << std::endl;
    }
};
class FinalDerived : public Derived1, public Derived2 {
public:
    FinalDerived() {
        std::cout << "FinalDerivedのコンストラクタが呼ばれました。" << std::endl;
    }
};
int main() {
    FinalDerived fd; // FinalDerivedのインスタンスを作成
    return 0;
}
Baseのコンストラクタが呼ばれました。
Derived1のコンストラクタが呼ばれました。
Derived2のコンストラクタが呼ばれました。
FinalDerivedのコンストラクタが呼ばれました。

よくある質問

基底クラスのコンストラクタは必ず呼び出されるのか?

基底クラスのコンストラクタは、派生クラスのコンストラクタが呼び出される際に自動的に呼び出されます。

ただし、基底クラスに引数付きのコンストラクタがある場合、派生クラスのコンストラクタで明示的に基底クラスのコンストラクタを呼び出す必要があります。

デフォルトコンストラクタが存在しない場合、基底クラスのコンストラクタが呼ばれないとコンパイルエラーが発生します。

派生クラスで基底クラスのデフォルトコンストラクタを省略できるか?

派生クラスで基底クラスのデフォルトコンストラクタを省略することは可能です。

基底クラスにデフォルトコンストラクタが存在する場合、派生クラスのコンストラクタで基底クラスのデフォルトコンストラクタを明示的に呼び出さなくても、自動的に呼び出されます。

ただし、基底クラスに引数付きのコンストラクタしかない場合は、派生クラスのコンストラクタで基底クラスのコンストラクタを明示的に呼び出す必要があります。

基底クラスのコンストラクタを呼び出す順序は変更できるか?

基底クラスのコンストラクタを呼び出す順序は、派生クラスのコンストラクタの初期化リストで指定することができます。

初期化リストにおいて、基底クラスのコンストラクタを呼び出す順序を変更することができます。

ただし、C++では、基底クラスのコンストラクタは、派生クラスのコンストラクタが呼び出される前に、宣言された順序に従って呼び出されるため、注意が必要です。

まとめ

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

特に、初期化リストの使い方や多重継承、仮想継承におけるコンストラクタの呼び出し方、さらには注意点についても触れました。

これらの知識を活用して、より効率的でエラーの少ないC++プログラムを作成することを目指してみてください。

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