[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クラスBase1Base2を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継承はライブラリの内部実装、テンプレートクラス、インターフェースの実装など、さまざまな場面で有効に活用できます。

クラス設計において、適切な継承の選択が重要です。

よくある質問

private継承はいつ使うべきか?

private継承は、基底クラスの実装を外部から隠蔽したい場合に使用すべきです。

具体的には、以下のような状況での利用が考えられます。

  • 内部実装の隠蔽: ライブラリやモジュールの内部実装を隠したい場合。
  • クラスのインターフェースをシンプルに保つ: 利用者に対して複雑な実装を隠し、シンプルなインターフェースを提供したい場合。
  • 基底クラスの機能を再利用しつつ、外部からのアクセスを制限したい場合: 基底クラスのメソッドを派生クラス内で利用したいが、外部からはアクセスさせたくない場合。

private継承とコンポジションの違いは?

private継承とコンポジションは、どちらもクラス間の関係を構築する手法ですが、以下のような違いがあります。

  • 継承: 基底クラスの特性を派生クラスに引き継ぎ、基底クラスのメンバーを派生クラス内で利用します。

派生クラスは基底クラスの一種と見なされます。

  • コンポジション: 他のクラスのインスタンスをメンバーとして持つことで、機能を組み合わせます。

コンポジションでは、クラス間の関係は has-a 関係となり、基底クラスの特性を引き継ぐことはありません。

以下のような状況で使い分けることができます。

  • 継承: 基底クラスの機能を拡張したい場合や、基底クラスの特性を持つ新しいクラスを作成したい場合。
  • コンポジション: 異なるクラスの機能を組み合わせたい場合や、クラスの柔軟性を高めたい場合。

private継承はパフォーマンスに影響するか?

一般的に、private継承自体がパフォーマンスに大きな影響を与えることはありません。

継承の種類に関わらず、クラスのメンバー関数の呼び出しやデータメンバーへのアクセスは、コンパイラによって最適化されるためです。

ただし、以下の点に注意が必要です。

  • オーバーヘッド: 継承を使用することで、オブジェクトのサイズが増加する場合がありますが、これは通常、クラスの設計や使用方法に依存します。
  • 仮想関数のオーバーヘッド: 基底クラスのメソッドが仮想関数である場合、オーバーヘッドが発生します。

これはprivate継承に限らず、publicやprotected継承でも同様です。

結論として、private継承はパフォーマンスに直接的な影響を与えることは少ないですが、クラス設計や使用方法によっては注意が必要です。

適切な設計を行うことで、パフォーマンスを最適化することが可能です。

まとめ

この記事では、C++におけるprivate継承の基本的な概念やそのメリット・デメリット、具体的な使用例について詳しく解説しました。

private継承は、基底クラスの実装を隠蔽しつつ、その機能を派生クラス内で再利用するための強力な手法であり、特にライブラリの設計やテンプレートクラス、インターフェースの実装において有効です。

これらの知識を活用し、クラス設計において適切な継承の選択を行うことで、より柔軟で保守性の高いコードを実現してみてください。

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