C++の継承は、プログラムの再利用性を高め、コードをより整理しやすくするための重要な機能です。
この記事では、継承の基本概念から始めて、公開継承、非公開継承、保護継承の違いや、基底クラスと派生クラスの関係、メンバーのアクセス制御、コンストラクタとデストラクタ、仮想関数と多態性、そして継承における注意点までを詳しく解説します。
初心者の方でも理解しやすいように、具体的なサンプルコードとその実行結果を交えながら説明していきますので、ぜひ参考にしてください。
継承の基本概念
継承とは何か
継承は、既存のクラス(基底クラスまたは親クラス)の特性を新しいクラス(派生クラスまたは子クラス)に引き継ぐ機能です。
これにより、コードの再利用性が向上し、プログラムの構造がより整理されます。
例えば、動物を表すクラスを基底クラスとし、犬や猫を表すクラスを派生クラスとすることで、共通の特性を持つクラスを効率的に作成できます。
継承のメリット
継承には以下のようなメリットがあります:
- コードの再利用:既存のクラスの機能を再利用することで、新しいクラスの開発が効率化されます。
- 保守性の向上:共通の機能を基底クラスにまとめることで、変更が必要な場合に一箇所を修正するだけで済みます。
- 拡張性の向上:新しい機能を追加する際に、既存のクラスを継承して拡張することが容易です。
継承の基本構文
C++での継承の基本構文は以下の通りです:
class 基底クラス {
public:
// 基底クラスのメンバー
};
class 派生クラス : public 基底クラス {
public:
// 派生クラスのメンバー
};
例えば、動物を表す基底クラスと犬を表す派生クラスを定義する場合、以下のようになります:
class Animal {
public:
void eat() {
std::cout << "Eating" << std::endl;
}
};
class Dog : public Animal {
public:
void bark() {
std::cout << "Barking" << std::endl;
}
};
この例では、Dogクラス
はAnimalクラス
を継承しており、Animalクラス
のeatメソッド
を利用できます。
継承の種類
公開継承(Public Inheritance)
公開継承の特徴
公開継承では、基底クラスのpublicメンバーとprotectedメンバーが派生クラスでも同じアクセスレベルで利用可能になります。
これは最も一般的な継承の形態です。
公開継承の使用例
以下は公開継承の例です:
class Base {
public:
int publicVar;
protected:
int protectedVar;
private:
int privateVar;
};
class Derived : public Base {
public:
void show() {
publicVar = 1; // OK
protectedVar = 2; // OK
// privateVar = 3; // エラー
}
};
非公開継承(Private Inheritance)
非公開継承の特徴
非公開継承では、基底クラスのpublicメンバーとprotectedメンバーが派生クラス内でprivateメンバーとして扱われます。
外部からはアクセスできません。
非公開継承の使用例
以下は非公開継承の例です:
class Base {
public:
int publicVar;
protected:
int protectedVar;
private:
int privateVar;
};
class Derived : private Base {
public:
void show() {
publicVar = 1; // OK
protectedVar = 2; // OK
// privateVar = 3; // エラー
}
};
保護継承(Protected Inheritance)
保護継承の特徴
保護継承では、基底クラスのpublicメンバーがprotectedメンバーとして派生クラスに引き継がれます。
外部からはアクセスできませんが、派生クラス内では利用可能です。
保護継承の使用例
以下は保護継承の例です:
class Base {
public:
int publicVar;
protected:
int protectedVar;
private:
int privateVar;
};
class Derived : protected Base {
public:
void show() {
publicVar = 1; // OK
protectedVar = 2; // OK
// privateVar = 3; // エラー
}
};
基底クラスと派生クラス
基底クラスの定義
基底クラスは他のクラスに継承されるクラスです。
基底クラスは共通の機能やデータを提供します。
class Base {
public:
void baseFunction() {
std::cout << "Base function" << std::endl;
}
};
派生クラスの定義
派生クラスは基底クラスを継承し、基底クラスの機能を拡張または変更します。
class Derived : public Base {
public:
void derivedFunction() {
std::cout << "Derived function" << std::endl;
}
};
基底クラスと派生クラスの関係
基底クラスと派生クラスの関係は is-a
関係と呼ばれます。
例えば、Dog
はAnimal
の一種であるため、Dogクラス
はAnimalクラス
を継承します。
メンバーのアクセス制御
publicメンバー
publicメンバーはクラスの外部からもアクセス可能です。
class MyClass {
public:
int publicVar;
};
protectedメンバー
protectedメンバーはクラス自身とその派生クラスからのみアクセス可能です。
class MyClass {
protected:
int protectedVar;
};
privateメンバー
privateメンバーはクラス自身からのみアクセス可能です。
class MyClass {
private:
int privateVar;
};
コンストラクタとデストラクタ
基底クラスのコンストラクタ
基底クラスのコンストラクタは派生クラスのコンストラクタから呼び出されます。
class Base {
public:
Base() {
std::cout << "Base constructor" << std::endl;
}
};
派生クラスのコンストラクタ
派生クラスのコンストラクタは基底クラスのコンストラクタを呼び出します。
class Derived : public Base {
public:
Derived() {
std::cout << "Derived constructor" << std::endl;
}
};
基底クラスのデストラクタ
基底クラスのデストラクタは派生クラスのデストラクタから呼び出されます。
class Base {
public:
~Base() {
std::cout << "Base destructor" << std::endl;
}
};
派生クラスのデストラクタ
派生クラスのデストラクタは基底クラスのデストラクタを呼び出します。
class Derived : public Base {
public:
~Derived() {
std::cout << "Derived destructor" << std::endl;
}
};
仮想関数と多態性
仮想関数の定義
仮想関数は基底クラスで定義され、派生クラスでオーバーライドされる関数です。
class Base {
public:
virtual void show() {
std::cout << "Base show" << std::endl;
}
};
純粋仮想関数
純粋仮想関数は基底クラスで定義され、派生クラスで必ずオーバーライドされる関数です。
class Base {
public:
virtual void show() = 0;
};
多態性の実現
多態性は、基底クラスのポインタを使って派生クラスのオーバーライドされた関数を呼び出すことです。
class Base {
public:
virtual void show() {
std::cout << "Base show" << std::endl;
}
};
class Derived : public Base {
public:
void show() override {
std::cout << "Derived show" << std::endl;
}
};
int main() {
Base* b = new Derived();
b->show(); // "Derived show" が出力される
delete b;
return 0;
}
仮想関数テーブル(vtable)
仮想関数テーブルは、仮想関数のアドレスを保持するテーブルです。
これにより、多態性が実現されます。
継承における注意点
ダイヤモンド継承問題
ダイヤモンド継承問題は、複数の派生クラスが同じ基底クラスを継承する際に発生する問題です。
class A {
public:
void show() {
std::cout << "A show" << std::endl;
}
};
class B : public A {};
class C : public A {};
class D : public B, public C {};
仮想継承
仮想継承は、ダイヤモンド継承問題を解決するための手法です。
class A {
public:
void show() {
std::cout << "A show" << std::endl;
}
};
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};
継承の設計上の注意点
継承を使用する際には、以下の点に注意する必要があります:
- 適切なアクセス制御:メンバーのアクセスレベルを適切に設定する。
- 多重継承の慎重な使用:多重継承は複雑さを増すため、慎重に使用する。
- 仮想関数の適切な使用:仮想関数を適切に使用して多態性を実現する。
実践例
基本的な継承の例
class Animal {
public:
void eat() {
std::cout << "Eating" << std::endl;
}
};
class Dog : public Animal {
public:
void bark() {
std::cout << "Barking" << std::endl;
}
};
int main() {
Dog myDog;
myDog.eat(); // "Eating" が出力される
myDog.bark(); // "Barking" が出力される
return 0;
}
多重継承の例
class A {
public:
void showA() {
std::cout << "A show" << std::endl;
}
};
class B {
public:
void showB() {
std::cout << "B show" << std::endl;
}
};
class C : public A, public B {};
int main() {
C obj;
obj.showA(); // "A show" が出力される
obj.showB(); // "B show" が出力される
return 0;
}
仮想継承の例
class A {
public:
void show() {
std::cout << "A show" << std::endl;
}
};
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};
int main() {
D obj;
obj.show(); // "A show" が出力される
return 0;
}
まとめ
C++の継承は、コードの再利用性や保守性を向上させる強力な機能です。
継承の基本概念から、公開継承、非公開継承、保護継承の違い、基底クラスと派生クラスの関係、メンバーのアクセス制御、コンストラクタとデストラクタ、仮想関数と多態性、継承における注意点、そして実践例までを詳しく解説しました。
これらの知識を活用して、効率的で保守性の高いプログラムを作成してください。