C++のpublic継承、private継承、protected継承を徹底解説

C++の継承はオブジェクト指向プログラミングの重要な概念であり、コードの再利用性や拡張性を高めるために使用されます。特に、public継承、private継承、protected継承の違いを理解することは、適切な設計を行うために不可欠です。本記事では、それぞれの継承の詳細や実装例、使い分け方について詳しく解説します。

目次

継承の基本概念

C++の継承は、既存のクラス(基底クラスまたは親クラス)のプロパティやメソッドを新しいクラス(派生クラスまたは子クラス)に引き継ぐ機能です。これにより、コードの再利用性が向上し、ソフトウェアの保守性が改善されます。継承は「is-a」関係を表し、例えば、「犬は動物である」という関係をクラスで表現する際に使用されます。基本的な継承の構文は以下の通りです。

class Base {
public:
    void baseMethod() {
        // 基底クラスのメソッド
    }
};

class Derived : public Base {
public:
    void derivedMethod() {
        // 派生クラスのメソッド
    }
};

public継承の詳細

public継承は、最も一般的に使用される継承形式であり、基底クラスのpublicメンバーが派生クラスでもpublicとして引き継がれます。これにより、基底クラスのインターフェースがそのまま派生クラスに公開されるため、利用者は基底クラスと同じ方法で派生クラスのオブジェクトを操作できます。public継承は「is-a」関係を明確に示すために使用されます。

メリット

  • 基底クラスのメソッドやプロパティがそのまま派生クラスで使用可能
  • インターフェースが統一されるため、コードの再利用性が高まる
  • ポリモーフィズムの実現が容易

使用例

以下の例では、Animalクラスを基底クラスとし、Dogクラスがpublic継承を行っています。

class Animal {
public:
    void eat() {
        // 基底クラスのメソッド
    }
};

class Dog : public Animal {
public:
    void bark() {
        // 派生クラスのメソッド
    }
};

int main() {
    Dog myDog;
    myDog.eat(); // Animalクラスのメソッドが使用可能
    myDog.bark(); // Dogクラスのメソッド
    return 0;
}

private継承の詳細

private継承は、基底クラスのpublicおよびprotectedメンバーを派生クラス内でprivateとして扱う継承形式です。これは、基底クラスの機能を内部で利用しつつ、派生クラスの外部には基底クラスのインターフェースを公開しない場合に有用です。private継承は「is-implemented-in-terms-of」関係を表すことが多いです。

特徴

  • 基底クラスのpublicおよびprotectedメンバーは派生クラス内でprivateとして扱われる
  • 基底クラスのメソッドやプロパティは派生クラス内で利用可能だが、外部からはアクセス不可
  • 基底クラスと派生クラスの関係を外部に隠蔽したい場合に適している

メリットとデメリット

  • メリット: 基底クラスの実装を利用しつつ、インターフェースをカプセル化できる
  • デメリット: ポリモーフィズムが利用できないため、基底クラスのポインタや参照を通じて派生クラスのオブジェクトを扱うことができない

使用例

以下の例では、Vehicleクラスを基底クラスとし、Carクラスがprivate継承を行っています。

class Vehicle {
public:
    void startEngine() {
        // 基底クラスのメソッド
    }
};

class Car : private Vehicle {
public:
    void drive() {
        startEngine(); // 基底クラスのメソッドを内部で使用
        // 派生クラスのメソッド
    }
};

int main() {
    Car myCar;
    myCar.drive(); // Carクラスのメソッド
    // myCar.startEngine(); // エラー: Vehicleクラスのメソッドは外部からアクセス不可
    return 0;
}

protected継承の詳細

protected継承は、基底クラスのpublicおよびprotectedメンバーを派生クラス内でprotectedとして扱う継承形式です。この形式では、派生クラスからは基底クラスのメンバーにアクセスできますが、派生クラスの外部からはアクセスできません。protected継承は主に、派生クラスとその派生クラス間で基底クラスのメンバーを共有したい場合に使用されます。

特徴

  • 基底クラスのpublicおよびprotectedメンバーは派生クラス内でprotectedとして扱われる
  • 基底クラスのメンバーは派生クラスとその子クラスからアクセス可能だが、外部からはアクセス不可
  • 継承階層内でのカプセル化を維持しながら、派生クラス間でのメンバー共有が可能

メリットとデメリット

  • メリット: 基底クラスの機能を派生クラスとその子クラスで共有しやすい
  • デメリット: 外部からのアクセスが制限されるため、インターフェースが隠蔽される

使用例

以下の例では、Personクラスを基底クラスとし、Employeeクラスがprotected継承を行っています。

class Person {
public:
    void setName(const std::string& name) {
        m_name = name;
    }
protected:
    std::string m_name;
};

class Employee : protected Person {
public:
    void setEmployeeName(const std::string& name) {
        setName(name); // 基底クラスのメソッドを内部で使用
    }
    std::string getEmployeeName() const {
        return m_name; // 基底クラスのprotectedメンバーにアクセス
    }
};

int main() {
    Employee emp;
    emp.setEmployeeName("John Doe");
    // emp.setName("Jane Doe"); // エラー: Personクラスのメソッドは外部からアクセス不可
    return 0;
}

public継承の実装例

public継承を使うと、基底クラスのpublicメンバーが派生クラスでもそのまま公開され、利用可能になります。以下に、public継承を使用した具体的なコード例を示します。

#include <iostream>
#include <string>

class Animal {
public:
    void eat() {
        std::cout << "Animal is eating." << std::endl;
    }
};

class Dog : public Animal {
public:
    void bark() {
        std::cout << "Dog is barking." << std::endl;
    }
};

int main() {
    Dog myDog;
    myDog.eat();  // Animalクラスのメソッドを使用
    myDog.bark(); // Dogクラスのメソッドを使用
    return 0;
}

この例では、AnimalクラスのeatメソッドがDogクラスでも利用可能になっています。これにより、DogクラスのオブジェクトはAnimalクラスの機能をそのまま利用しつつ、独自の機能(barkメソッド)を追加しています。public継承を使用することで、基底クラスのインターフェースがそのまま派生クラスにも引き継がれ、ポリモーフィズムの実現も容易になります。

private継承の実装例

private継承を使うと、基底クラスのpublicおよびprotectedメンバーが派生クラス内でprivateとして扱われ、外部からアクセスできなくなります。以下に、private継承を使用した具体的なコード例を示します。

#include <iostream>
#include <string>

class Vehicle {
public:
    void startEngine() {
        std::cout << "Engine started." << std::endl;
    }
};

class Car : private Vehicle {
public:
    void drive() {
        startEngine(); // 基底クラスのメソッドを内部で使用
        std::cout << "Car is driving." << std::endl;
    }
};

int main() {
    Car myCar;
    myCar.drive(); // Carクラスのメソッドを使用
    // myCar.startEngine(); // エラー: Vehicleクラスのメソッドは外部からアクセス不可
    return 0;
}

この例では、VehicleクラスのstartEngineメソッドがCarクラス内でのみ利用可能であり、Carクラスの外部からはアクセスできません。private継承を使用することで、基底クラスの機能を内部で利用しつつ、派生クラスのインターフェースをカプセル化しています。これにより、基底クラスの実装詳細を隠蔽し、派生クラスのインターフェースを明確にすることができます。

protected継承の実装例

protected継承を使うと、基底クラスのpublicおよびprotectedメンバーが派生クラス内でprotectedとして扱われ、派生クラスおよびその子クラスからアクセス可能になりますが、外部からはアクセスできなくなります。以下に、protected継承を使用した具体的なコード例を示します。

#include <iostream>
#include <string>

class Person {
public:
    void setName(const std::string& name) {
        m_name = name;
    }
protected:
    std::string m_name;
};

class Employee : protected Person {
public:
    void setEmployeeName(const std::string& name) {
        setName(name); // 基底クラスのメソッドを内部で使用
    }
    std::string getEmployeeName() const {
        return m_name; // 基底クラスのprotectedメンバーにアクセス
    }
};

class Manager : public Employee {
public:
    void promote() {
        std::cout << "Promoting " << m_name << std::endl; // Employeeクラスのprotectedメンバーにアクセス
    }
};

int main() {
    Manager mgr;
    mgr.setEmployeeName("Alice Johnson");
    mgr.promote(); // Managerクラスのメソッドを使用
    // mgr.setName("Bob Smith"); // エラー: Personクラスのメソッドは外部からアクセス不可
    return 0;
}

この例では、PersonクラスのsetNameメソッドとm_nameメンバーがEmployeeクラスおよびその派生クラスManagerクラスからアクセス可能ですが、Managerクラスの外部からはアクセスできません。protected継承を使用することで、基底クラスの機能を派生クラスとその子クラスで共有しつつ、外部からのアクセスを制限しています。

継承の選択基準

C++でpublic継承、private継承、protected継承を使い分ける際の選択基準について説明します。それぞれの継承形式は、異なる用途や設計要件に応じて使い分ける必要があります。

public継承

  • 使用場面: 基底クラスのインターフェースをそのまま派生クラスに引き継ぎたい場合に使用します。
  • : 「動物」クラスを「犬」クラスが継承するなど、明確な「is-a」関係を表現する場合。
  • メリット: 基底クラスのメンバーがそのまま派生クラスで公開され、ポリモーフィズムが利用可能。

private継承

  • 使用場面: 基底クラスの機能を内部で利用したいが、外部にはその関係を隠蔽したい場合に使用します。
  • : 「車」クラスが「エンジン」クラスを継承するなど、実装詳細を隠蔽する必要がある場合。
  • メリット: 基底クラスの実装を再利用しつつ、派生クラスのインターフェースをカプセル化できる。

protected継承

  • 使用場面: 基底クラスの機能を派生クラスとその子クラスで共有したい場合に使用します。
  • : 「従業員」クラスを「マネージャー」クラスが継承するなど、継承階層内でのメンバー共有が必要な場合。
  • メリット: 基底クラスの機能を継承階層内で共有しつつ、外部からのアクセスを制限できる。

使い分けのまとめ

  • public継承: インターフェースの継承とポリモーフィズムが必要な場合。
  • private継承: 実装の再利用とカプセル化が必要な場合。
  • protected継承: 継承階層内での機能共有が必要な場合。

応用例と演習問題

継承の理解を深めるために、応用例や演習問題を紹介します。これらを通じて、public継承、private継承、protected継承の使い分けを実践的に学びましょう。

応用例1: 複数の継承の組み合わせ

複数の継承形式を組み合わせて使うことで、複雑なオブジェクト間の関係を表現できます。

#include <iostream>
#include <string>

class Person {
public:
    void setName(const std::string& name) {
        m_name = name;
    }
    std::string getName() const {
        return m_name;
    }
protected:
    std::string m_name;
};

class Employee : public Person {
public:
    void setEmployeeID(int id) {
        m_employeeID = id;
    }
    int getEmployeeID() const {
        return m_employeeID;
    }
private:
    int m_employeeID;
};

class Manager : protected Employee {
public:
    void setDepartment(const std::string& department) {
        m_department = department;
    }
    std::string getDepartment() const {
        return m_department;
    }
    void displayInfo() const {
        std::cout << "Name: " << getName() << ", Employee ID: " << getEmployeeID() 
                  << ", Department: " << m_department << std::endl;
    }
private:
    std::string m_department;
};

int main() {
    Manager mgr;
    mgr.setName("Alice Johnson");
    mgr.setEmployeeID(12345);
    mgr.setDepartment("HR");
    mgr.displayInfo(); // Name: Alice Johnson, Employee ID: 12345, Department: HR
    return 0;
}

演習問題1: クラス設計

以下の要件に従ってクラスを設計してください。

  • 基底クラス Shape を作成し、メソッド draw() を定義。
  • Shape をpublic継承する Circle クラスを作成し、draw() メソッドをオーバーライド。
  • Shape をprotected継承する Polygon クラスを作成し、追加のメソッド sides() を定義。

演習問題2: 継承の使い分け

以下の要件に基づき、適切な継承形式を選んでクラスを実装してください。

  • 基底クラス Machine があり、そのpublicメソッド start() を持つ。
  • Machine を基に Robot クラスをpublic継承し、メソッド operate() を追加。
  • Machine を基に Vehicle クラスをprivate継承し、メソッド drive() を追加。

よくある間違いとその対策

C++の継承においては、初心者が犯しがちな間違いがいくつかあります。これらの間違いを理解し、適切な対策を取ることで、より効果的な継承の利用が可能になります。

間違い1: 不適切な継承形式の選択

  • 問題: public継承が適している場面でprivate継承を使用してしまうと、外部からアクセスできなくなるため、基底クラスのインターフェースが利用できなくなります。
  • 対策: クラス間の「is-a」関係がある場合にはpublic継承を使用し、実装の詳細を隠蔽する必要がある場合にはprivate継承を選択する。

間違い2: 基底クラスのメンバーの不適切なアクセス

  • 問題: 基底クラスのprivateメンバーに派生クラスからアクセスしようとしてエラーが発生する。
  • 対策: 基底クラスのメンバーにアクセスする際には、publicまたはprotectedのアクセサメソッドを利用する。

間違い3: コンストラクタとデストラクタの扱い

  • 問題: 基底クラスのコンストラクタやデストラクタが適切に呼び出されない。
  • 対策: 派生クラスのコンストラクタで基底クラスのコンストラクタを呼び出し、デストラクタはvirtualにすることで、正しくリソースを解放する。

間違い4: ポリモーフィズムの誤用

  • 問題: ポリモーフィズムを実現するために、基底クラスのメソッドがvirtualでないため、正しく動作しない。
  • 対策: ポリモーフィズムを利用する際には、基底クラスのメソッドをvirtualとして宣言し、派生クラスでオーバーライドする。

例外処理の考慮

  • 問題: 継承関係にあるクラスで例外処理を正しく実装していないため、予期しない動作をする。
  • 対策: 例外処理を適切に設計し、基底クラスおよび派生クラスで一貫性を保つ。
class Base {
public:
    virtual ~Base() {}
    virtual void doSomething() {
        // 基底クラスの実装
    }
};

class Derived : public Base {
public:
    void doSomething() override {
        // 派生クラスの実装
    }
};

これらのポイントを理解し、実践することで、C++の継承をより効果的に利用することができます。

まとめ

本記事では、C++におけるpublic継承、private継承、protected継承の違いと使い方について詳しく解説しました。それぞれの継承形式には特有の利点と注意点があり、適切に使い分けることでコードの再利用性や保守性を高めることができます。基本概念から実装例、応用例や演習問題まで網羅することで、継承の理解が深まったことでしょう。継承の選択基準やよくある間違いを参考に、より洗練されたクラス設計を心掛けてください。

これで構成全体が完了しました。各セクションの内容に沿って具体的なWeb記事を作成できますので、各項目ごとにさらに具体的な指示があればお知らせください。

コメント

コメントする

目次