[C++] privateで継承したときの派生クラスの挙動を解説
C++において、クラスをprivateで継承すると、基底クラスのpublicおよびprotectedメンバーは派生クラス内ではprivateメンバーとして扱われます。
これにより、派生クラスの外部からは基底クラスのメンバーにアクセスできなくなります。
つまり、派生クラスのオブジェクトを通じて基底クラスのメンバーを直接利用することはできません。
private継承は「実装の詳細を隠す」ために使われ、基底クラスの機能を内部的に利用したい場合に有効です。
派生クラス内では基底クラスのメンバーにアクセス可能ですが、派生クラスの外部からはアクセスできないため、基底クラスのインターフェースを隠蔽することができます。
- private継承の基本的な概念
- publicおよびprotected継承との違い
- private継承のメリットとデメリット
- 実装方法や具体的な使用例
- 適切な継承の選択が重要な理由
private継承とは
C++におけるprivate継承は、基底クラスのメンバーを派生クラス内でのみアクセス可能にする継承の方法です。
これにより、基底クラスのインターフェースを外部に公開せず、実装の詳細を隠すことができます。
private継承を使用することで、クラスの設計をより厳密に制御することが可能になります。
private継承の基本
private継承は、基底クラスのメンバーを派生クラス内でのみ利用できるようにするための手法です。
以下のように、クラスの定義でprivate
キーワードを使用します。
#include <iostream>
class Base {
public:
void show() {
std::cout << "Base class method" << std::endl;
}
};
class Derived : private Base {
public:
void display() {
show(); // Baseクラスのメソッドを呼び出す
}
};
int main() {
Derived d;
d.display(); // Derivedクラスのメソッドを呼び出す
// d.show(); // エラー: show()はアクセスできない
return 0;
}
Base class method
この例では、Derivedクラス
がBaseクラス
をprivateで継承しています。
Derivedクラス
内ではshow()メソッド
を呼び出すことができますが、外部からはアクセスできません。
public継承との違い
public継承では、基底クラスのメンバーは派生クラスのインスタンスからもアクセス可能です。
以下の表に、private継承とpublic継承の違いを示します。
特徴 | private継承 | public継承 |
---|---|---|
アクセス制御 | 派生クラス内のみ | 派生クラスと外部からアクセス可能 |
基底クラスのメンバー | 派生クラス内でのみ利用可能 | 派生クラスと外部から利用可能 |
継承の目的 | 実装の隠蔽 | インターフェースの拡張 |
protected継承との違い
protected継承は、基底クラスのメンバーを派生クラスとその派生クラスからのみアクセス可能にします。
以下の表に、private継承とprotected継承の違いを示します。
特徴 | private継承 | protected継承 |
---|---|---|
アクセス制御 | 派生クラス内のみ | 派生クラスとその派生クラスからアクセス可能 |
基底クラスのメンバー | 派生クラス内でのみ利用可能 | 派生クラスとその派生クラスから利用可能 |
継承の目的 | 実装の隠蔽 | 派生クラスの拡張 |
このように、private継承、public継承、protected継承はそれぞれ異なる目的とアクセス制御を持っており、適切な継承の選択がクラス設計において重要です。
private継承のメリットとデメリット
private継承は、特定の状況で非常に有用ですが、メリットとデメリットがあります。
以下にそれぞれのポイントを詳しく解説します。
メリット:実装の隠蔽
private継承の最大のメリットは、基底クラスの実装を外部から隠すことができる点です。
これにより、クラスの内部構造を変更しても、外部のコードに影響を与えずに済みます。
クラスの利用者は、基底クラスの詳細を知らずに、派生クラスのインターフェースのみを利用することができます。
#include <iostream>
class Base {
public:
void show() {
std::cout << "Base class method" << std::endl;
}
};
class Derived : private Base {
public:
void display() {
show(); // Baseクラスのメソッドを呼び出す
}
};
int main() {
Derived d;
d.display(); // Baseクラスのメソッドを呼び出す
// d.show(); // エラー: show()はアクセスできない
return 0;
}
Base class method
この例では、Derivedクラス
がBaseクラス
のメソッドを利用していますが、外部からはアクセスできません。
メリット:基底クラスの機能の再利用
private継承を使用することで、基底クラスの機能を再利用しつつ、外部にその機能を公開しないことができます。
これにより、基底クラスのメソッドやデータメンバーを派生クラス内で自由に利用でき、コードの重複を避けることができます。
デメリット:柔軟性の低下
private継承のデメリットの一つは、柔軟性が低下することです。
基底クラスのメンバーに外部からアクセスできないため、他のクラスとの連携が難しくなります。
特に、基底クラスの機能を他のクラスで利用したい場合、private継承ではその機能を再利用できません。
デメリット:コードの可読性
private継承を使用すると、コードの可読性が低下する可能性があります。
外部の開発者がクラスの設計を理解する際、基底クラスのメンバーがアクセスできないため、クラスの動作を把握するのが難しくなることがあります。
特に大規模なプロジェクトでは、クラス間の関係を理解するために追加のドキュメントが必要になることがあります。
- メリット
- 実装の隠蔽
- 基底クラスの機能の再利用
- デメリット
- 柔軟性の低下
- コードの可読性の低下
このように、private継承には明確なメリットとデメリットが存在します。
クラス設計の際には、これらの要素を考慮して適切な継承の方法を選択することが重要です。
private継承の具体的な使用例
private継承は、特定の状況で非常に効果的に利用されます。
以下に、具体的な使用例をいくつか紹介します。
基底クラスのメンバーの隠蔽
private継承を使用することで、基底クラスのメンバーを派生クラス内でのみ利用可能にし、外部からのアクセスを防ぐことができます。
これにより、クラスの内部実装を隠蔽し、利用者に対してシンプルなインターフェースを提供できます。
#include <iostream>
class Base {
public:
void show() {
std::cout << "Base class method" << std::endl;
}
private:
int secretData = 42; // 外部からはアクセスできない
};
class Derived : private Base {
public:
void display() {
show(); // Baseクラスのメソッドを呼び出す
// secretDataはアクセスできない
}
};
int main() {
Derived d;
d.display(); // Baseクラスのメソッドを呼び出す
// d.secretData; // エラー: secretDataはアクセスできない
return 0;
}
Base class method
この例では、Baseクラス
のsecretData
メンバーはprivateであるため、外部からはアクセスできません。
派生クラスでの基底クラスメンバーの利用
private継承を使用することで、派生クラス内で基底クラスのメンバーを自由に利用できます。
これにより、基底クラスの機能を再利用しつつ、外部からのアクセスを制限することができます。
#include <iostream>
class Base {
public:
void show() {
std::cout << "Base class method" << std::endl;
}
};
class Derived : private Base {
public:
void display() {
show(); // Baseクラスのメソッドを呼び出す
}
};
int main() {
Derived d;
d.display(); // Baseクラスのメソッドを呼び出す
// d.show(); // エラー: show()はアクセスできない
return 0;
}
Base class method
この例では、Derivedクラス
がBaseクラス
のshow()メソッド
を利用していますが、外部からはアクセスできません。
実装の詳細を隠すデザインパターン
private継承は、特定のデザインパターンにおいても利用されます。
例えば、コンポジットパターンやファサードパターンでは、内部の実装を隠蔽し、シンプルなインターフェースを提供するためにprivate継承が役立ちます。
#include <iostream>
class Engine {
public:
void start() {
std::cout << "Engine started" << std::endl;
}
};
class Car : private Engine {
public:
void drive() {
start(); // Engineクラスのメソッドを呼び出す
std::cout << "Car is driving" << std::endl;
}
};
int main() {
Car myCar;
myCar.drive(); // Carクラスのメソッドを呼び出す
// myCar.start(); // エラー: start()はアクセスできない
return 0;
}
Engine started
Car is driving
この例では、Carクラス
がEngineクラス
をprivateで継承しています。
Carクラス
の利用者は、Engineクラス
の詳細を知らずにCarクラス
のインターフェースを利用できます。
このように、private継承は基底クラスのメンバーを隠蔽し、派生クラス内での利用を可能にするため、特定のデザインパターンやクラス設計において非常に有用です。
private継承の実装方法
private継承を正しく実装するためには、いくつかの基本的な構文や注意点があります。
以下に、private継承の実装方法について詳しく解説します。
基本的な構文
private継承は、クラス定義の際にprivate
キーワードを使用して行います。
以下の例では、Baseクラス
をDerivedクラス
がprivateで継承しています。
#include <iostream>
class Base {
public:
void show() {
std::cout << "Base class method" << std::endl;
}
};
class Derived : private Base { // private継承
public:
void display() {
show(); // Baseクラスのメソッドを呼び出す
}
};
int main() {
Derived d;
d.display(); // Baseクラスのメソッドを呼び出す
// d.show(); // エラー: show()はアクセスできない
return 0;
}
Base class method
この例では、Derivedクラス
がBaseクラス
をprivateで継承しており、show()メソッド
はDerivedクラス
内でのみ利用可能です。
コンストラクタとデストラクタの扱い
private継承を使用する場合、基底クラスのコンストラクタやデストラクタは派生クラスのコンストラクタで明示的に呼び出す必要があります。
以下の例では、基底クラスのコンストラクタを派生クラスのコンストラクタで呼び出しています。
#include <iostream>
class Base {
public:
Base() {
std::cout << "Base class constructor" << std::endl;
}
~Base() {
std::cout << "Base class destructor" << std::endl;
}
};
class Derived : private Base {
public:
Derived() : Base() { // Baseクラスのコンストラクタを呼び出す
std::cout << "Derived class constructor" << std::endl;
}
~Derived() {
std::cout << "Derived class destructor" << std::endl;
}
};
int main() {
Derived d; // Derivedクラスのインスタンスを生成
return 0;
}
Base class constructor
Derived class constructor
Derived class destructor
Base class destructor
この例では、Derivedクラス
のコンストラクタでBaseクラス
のコンストラクタを呼び出しています。
デストラクタも同様に、基底クラスのデストラクタが自動的に呼び出されます。
メンバー関数のオーバーライド
private継承を使用する場合、基底クラスのメンバー関数を派生クラスでオーバーライドすることができます。
オーバーライドしたメソッドは、派生クラス内で利用可能ですが、外部からはアクセスできません。
#include <iostream>
class Base {
public:
virtual void show() {
std::cout << "Base class method" << std::endl;
}
};
class Derived : private Base {
public:
void show() override { // Baseクラスのメソッドをオーバーライド
std::cout << "Derived class method" << std::endl;
}
void display() {
show(); // オーバーライドしたメソッドを呼び出す
}
};
int main() {
Derived d;
d.display(); // Derivedクラスのメソッドを呼び出す
// d.show(); // エラー: show()はアクセスできない
return 0;
}
Derived class method
この例では、Derivedクラス
がBaseクラス
のshow()メソッド
をオーバーライドしています。
display()メソッド
内でオーバーライドしたshow()メソッド
を呼び出すことができますが、外部からはアクセスできません。
このように、private継承の実装方法には基本的な構文、コンストラクタとデストラクタの扱い、メンバー関数のオーバーライドに関する注意点があります。
これらを理解することで、効果的にprivate継承を利用することができます。
private継承と他の継承の比較
C++における継承には、public、protected、privateの3つのアクセス修飾子があります。
それぞれの継承方法には異なる特性があり、適切な選択がクラス設計において重要です。
以下に、private継承と他の継承方法との比較を示します。
public継承との比較
public継承は、基底クラスのメンバーを派生クラスとその利用者がアクセスできるようにします。
一方、private継承は、基底クラスのメンバーを派生クラス内でのみ利用可能にし、外部からのアクセスを制限します。
以下の表に、両者の違いを示します。
特徴 | private継承 | public継承 |
---|---|---|
アクセス制御 | 派生クラス内のみ | 派生クラスと外部からアクセス可能 |
基底クラスのメンバー | 派生クラス内でのみ利用可能 | 派生クラスと外部から利用可能 |
継承の目的 | 実装の隠蔽 | インターフェースの拡張 |
public継承は、基底クラスの機能を外部に公開する場合に適しており、private継承は、基底クラスの実装を隠蔽したい場合に有効です。
protected継承との比較
protected継承は、基底クラスのメンバーを派生クラスとその派生クラスからのみアクセス可能にします。
private継承との違いは、protected継承ではさらに派生クラスの派生クラスからも基底クラスのメンバーにアクセスできる点です。
以下の表に、両者の違いを示します。
特徴 | private継承 | protected継承 |
---|---|---|
アクセス制御 | 派生クラス内のみ | 派生クラスとその派生クラスからアクセス可能 |
基底クラスのメンバー | 派生クラス内でのみ利用可能 | 派生クラスとその派生クラスから利用可能 |
継承の目的 | 実装の隠蔽 | 派生クラスの拡張 |
protected継承は、基底クラスの機能を派生クラスの派生クラスでも利用したい場合に適していますが、private継承は外部からのアクセスを完全に制限したい場合に有効です。
多重継承におけるprivate継承の役割
C++では、多重継承が可能であり、複数の基底クラスを持つ派生クラスを定義できます。
この場合、private継承は特に重要な役割を果たします。
多重継承を使用することで、異なる基底クラスの機能を組み合わせることができますが、基底クラスのメンバーが外部からアクセスできないようにするためにprivate継承を利用することができます。
#include <iostream>
class Base1 {
public:
void show() {
std::cout << "Base1 class method" << std::endl;
}
};
class Base2 {
public:
void display() {
std::cout << "Base2 class method" << std::endl;
}
};
class Derived : private Base1, private Base2 { // 多重継承
public:
void callMethods() {
show(); // Base1のメソッドを呼び出す
display(); // Base2のメソッドを呼び出す
}
};
int main() {
Derived d;
d.callMethods(); // Derivedクラスのメソッドを呼び出す
// d.show(); // エラー: show()はアクセスできない
// d.display(); // エラー: display()はアクセスできない
return 0;
}
Base1 class method
Base2 class method
この例では、Derivedクラス
がBase1
とBase2
をprivateで継承しています。
これにより、Derivedクラス
内で両方の基底クラスのメソッドを利用できますが、外部からはアクセスできません。
多重継承におけるprivate継承は、基底クラスの実装を隠蔽しつつ、必要な機能を組み合わせるために非常に有効です。
このように、private継承は他の継承方法と比較して、特定の目的に応じた柔軟なクラス設計を可能にします。
適切な継承の選択が、クラスの設計や実装において重要な要素となります。
応用例
private継承は、特定の状況で非常に有用です。
以下に、いくつかの応用例を紹介します。
ライブラリの内部実装での利用
ライブラリを設計する際、内部の実装を隠蔽するためにprivate継承を利用することがあります。
これにより、ライブラリの利用者は、基底クラスの詳細を知らずに、提供されるインターフェースのみを利用できます。
例えば、データ構造の実装を隠蔽する場合に有効です。
#include <iostream>
#include <vector>
class InternalData {
public:
void add(int value) {
data.push_back(value);
}
void show() {
for (int val : data) {
std::cout << val << " ";
}
std::cout << std::endl;
}
private:
std::vector<int> data; // 内部データを隠蔽
};
class PublicInterface : private InternalData { // private継承
public:
void addValue(int value) {
add(value); // InternalDataのメソッドを呼び出す
}
void displayValues() {
show(); // InternalDataのメソッドを呼び出す
}
};
int main() {
PublicInterface pi;
pi.addValue(10);
pi.addValue(20);
pi.displayValues(); // 内部データを表示
// pi.show(); // エラー: show()はアクセスできない
return 0;
}
10 20
この例では、PublicInterfaceクラス
がInternalDataクラス
をprivateで継承しており、内部データの詳細を隠蔽しています。
テンプレートクラスでのprivate継承
テンプレートクラスにおいてもprivate継承を利用することができます。
これにより、特定の型に対して基底クラスの機能を再利用しつつ、外部からのアクセスを制限することができます。
#include <iostream>
template <typename T>
class Base {
public:
void show() {
std::cout << "Base class method for type: " << typeid(T).name() << std::endl;
}
};
template <typename T>
class Derived : private Base<T> { // private継承
public:
void display() {
this->show(); // Baseクラスのメソッドを呼び出す
}
};
int main() {
Derived<int> d;
d.display(); // Baseクラスのメソッドを呼び出す
// d.show(); // エラー: show()はアクセスできない
return 0;
}
Base class method for type: int
この例では、Derivedクラス
がテンプレート型T
を持つBaseクラス
をprivateで継承しています。
これにより、Derivedクラス
内でBaseクラス
のメソッドを利用できますが、外部からはアクセスできません。
インターフェースの実装におけるprivate継承
インターフェースを実装する際にprivate継承を利用することで、基底クラスのメソッドを実装しつつ、外部からのアクセスを制限することができます。
これにより、クラスの利用者はインターフェースのみを利用し、実装の詳細を知らずに済みます。
#include <iostream>
class IShape {
public:
virtual void draw() = 0; // 純粋仮想関数
};
class Circle : private IShape { // private継承
public:
void draw() override {
std::cout << "Drawing Circle" << std::endl;
}
void render() {
draw(); // IShapeのメソッドを呼び出す
}
};
int main() {
Circle c;
c.render(); // Circleの描画メソッドを呼び出す
// c.draw(); // エラー: draw()はアクセスできない
return 0;
}
Drawing Circle
この例では、Circleクラス
がIShape
インターフェースをprivateで継承しています。
これにより、Circleクラス
の利用者はdraw()メソッド
にアクセスできず、render()メソッド
を通じてのみ描画機能を利用できます。
このように、private継承はライブラリの内部実装、テンプレートクラス、インターフェースの実装など、さまざまな場面で有効に活用できます。
クラス設計において、適切な継承の選択が重要です。
よくある質問
まとめ
この記事では、C++におけるprivate継承の基本的な概念やそのメリット・デメリット、具体的な使用例について詳しく解説しました。
private継承は、基底クラスの実装を隠蔽しつつ、その機能を派生クラス内で再利用するための強力な手法であり、特にライブラリの設計やテンプレートクラス、インターフェースの実装において有効です。
これらの知識を活用し、クラス設計において適切な継承の選択を行うことで、より柔軟で保守性の高いコードを実現してみてください。