読み込み中...

初心者でも簡単!C++におけるクラスと継承の5つの鍵

claX++のクラスを継承を徹底解説するイメージ C++
この記事は約18分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

この記事では、初心者でも理解しやすいように、C++の基本的な概念から始めて、クラスと継承の詳細にわたる解説を行います。

C++のクラスと継承を理解することで、より効率的で再利用可能なコードの作成が可能になります。

この記事を通じて、C++に関する基本的な知識を身につけ、さらに深い理解を得ることを目指します。

●C++とは

C++は、システムプログラミングやアプリケーション開発に広く使用されている汎用プログラミング言語です。

C言語の上位互換であるため、C言語の特性を継承しつつ、オブジェクト指向プログラミング、例外処理、強力な型チェックなどの機能が追加されています。

C++は、そのパフォーマンスの高さと柔軟性から、ゲーム開発、システムプログラミング、高性能計算など、幅広い分野で利用されています。

○C++の基本概念

C++で最も重要な概念の一つが「オブジェクト指向プログラミング」です。

これは、プログラムをオブジェクトという単位で構築することを意味します。

オブジェクトはデータ(属性)とそれを操作する手段(メソッド)をカプセル化し、プログラムの再利用性とメンテナンス性を高めます。

また、C++では強力な型システムを有し、静的型付けによってコードの安全性を向上させることが可能です。

○C++の特徴と強み

C++の最大の特徴は、その高いパフォーマンスと効率性です。

低レベルの操作が可能であり、メモリ管理やハードウェアレベルのアクセスを直接行うことができます。

これにより、リソースが限られた環境や、高い処理速度が求められるアプリケーションに適しています。

さらに、オブジェクト指向だけでなく、手続き型プログラミングや汎用プログラミングなど、多様なプログラミングパラダイムをサポートしているため、非常に幅広い用途に使用することが可能です。

●C++のクラス基本

C++におけるクラスは、オブジェクト指向プログラミングの基本要素です。

クラスはデータの構造とそれを操作するためのメソッドを一つにまとめたもので、現実世界の物体や概念をプログラム内で表現するための強力なツールです。

○クラスの定義と構造

C++でクラスを定義するには、classキーワードを使用します。クラスの中には、メンバ変数(クラス内で保持されるデータ)とメンバ関数(クラスの振る舞いや操作を定義する関数)を定義できます。

クラスは、データのカプセル化、継承、ポリモーフィズムなどのオブジェクト指向の特徴をサポートしています。

クラスの基本的な構造は下記の通りです。

class クラス名 {
    // メンバ変数
    データ型 変数名;

    // メンバ関数
    戻り値の型 関数名(引数リスト) {
        // 関数の本体
    }
};

○コンストラクタとデストラクタ

コンストラクタは、クラスのオブジェクトが生成される時に自動的に呼び出される特別なメソッドです。

初期化のために用いられ、クラス名と同じ名前を持ちます。

デストラクタは、オブジェクトが破棄されるときに呼び出されるメソッドで、クラス名の前に~をつけた名前を持ちます。

○メンバ変数とメンバ関数

メンバ変数は、クラス内で保持されるデータを表し、メンバ関数はそのデータに対する操作を定義します。

メンバ関数を通じて、クラスの外部からメンバ変数へのアクセスを制御し、データのカプセル化を実現します。

○サンプルコード1:基本的なクラスの作成

ここでは、C++で基本的なクラスを作成するサンプルコードを紹介します。

この例では、Carクラスを定義し、いくつかのメンバ変数とメンバ関数を持たせています。

#include <iostream>
using namespace std;

class Car {
public:
    string brand;
    int year;

    Car(string b, int y) {
        brand = b;
        year = y;
    }

    void displayInfo() {
        cout << "ブランド: " << brand << endl;
        cout << "製造年: " << year << endl;
    }
};

int main() {
    Car car1("Toyota", 2022);
    car1.displayInfo();
    return 0;
}

このコードでは、Carクラスにbrandyearというメンバ変数があり、コンストラクタで初期化されています。

displayInfoメンバ関数は、車の情報を表示します。

main関数内でCarクラスのオブジェクトを作成し、その情報を表示しています。

●C++での継承の基礎

C++での継承は、クラス間でコードを再利用する強力なメカニズムです。

継承を使うことで、既存のクラス(基底クラス)のプロパティやメソッドを新しいクラス(派生クラス)に引き継ぐことができます。

これにより、コードの重複を減らし、プログラムの整理とメンテナンスを容易にします。

○継承の概念と利点

継承の主な利点は、コードの再利用性と拡張性の向上です。

基底クラスのコードを変更することなく、派生クラスで新しい機能を追加したり、既存の機能をオーバーライド(上書き)したりできます。

継承は、関連するクラス間の階層的な関係を作成し、オブジェクト指向プログラミングのポリモーフィズムと密接に関連しています。

○基底クラスと派生クラス

基底クラス(または親クラス)は、派生クラス(または子クラス)に機能を提供するクラスです。

派生クラスは基底クラスのすべての公開(public)および保護(protected)メンバを継承し、それに独自のメンバを追加することができます。

C++では、継承は下記のように宣言されます。

class 派生クラス名 : アクセス指定子 基底クラス名 {
    // 派生クラスのメンバ
};

ここで、アクセス指定子はpublicprotectedprivateのいずれかを指定します。

○サンプルコード2:単純な継承の実装

以下は、C++で単純な継承を実装するサンプルコードです。

この例では、Vehicleクラス(基底クラス)とその派生クラスであるCarクラスを定義しています。

#include <iostream>
using namespace std;

// 基底クラス Vehicle
class Vehicle {
public:
    string brand;
    Vehicle(string b) : brand(b) {}

    void display() {
        cout << "ブランド: " << brand << endl;
    }
};

// Vehicleを継承した派生クラス Car
class Car : public Vehicle {
public:
    int doors;
    Car(string b, int d) : Vehicle(b), doors(d) {}

    void display() {
        Vehicle::display();
        cout << "ドア数: " << doors << endl;
    }
};

int main() {
    Car car1("Toyota", 4);
    car1.display();
    return 0;
}

このコードでは、CarクラスがVehicleクラスからbrandメンバ変数とdisplayメンバ関数を継承しています。

Carクラスには独自のメンバ変数doorsと、display関数のオーバーライドが追加されています。

main関数ではCarクラスのオブジェクトを作成し、そのdisplay関数を呼び出しています。

●クラスと継承の応用テクニック

C++のクラスと継承の応用は、より高度なプログラミング技術への扉を開きます。

ここでは、ポリモーフィズム、抽象クラス、インターフェイスといった概念に焦点を当てて解説します。

○ポリモーフィズムとは

ポリモーフィズムは、異なるクラスのオブジェクトが同じインターフェイスを共有することを可能にする概念です。

これにより、異なるクラスのオブジェクトを一つのインターフェイスを通して操作できるようになります。

ポリモーフィズムは、継承とオーバーライディング(メソッドの再定義)を利用して実現されます。

○抽象クラスとインターフェイス

抽象クラスは、一部または全部のメソッドが定義されていないクラスです。これらのメソッドは派生クラスで実装される必要があります。

C++では、純粋仮想関数(= 0で終わる仮想関数)を一つ以上持つクラスが抽象クラスになります。

インターフェイスは、すべてのメソッドが純粋仮想関数のクラスで、派生クラスに特定のメソッド群の実装を強制します。

○サンプルコード3:ポリモーフィズムの使用例

ここでは、C++でポリモーフィズムを使用するサンプルコードを紹介します。

Shapeクラスを基底クラスとし、その派生クラスとしてCircleRectangleを定義しています。

#include <iostream>
using namespace std;

// 基底クラス Shape
class Shape {
public:
    virtual void draw() = 0; // 純粋仮想関数
};

// Shapeを継承した派生クラス Circle
class Circle : public Shape {
public:
    void draw() override {
        cout << "円を描く" << endl;
    }
};

// Shapeを継承した派生クラス Rectangle
class Rectangle : public Shape {
public:
    void draw() override {
        cout << "四角形を描く" << endl;
    }
};

int main() {
    Shape* shapes[2];
    shapes[0] = new Circle();
    shapes[1] = new Rectangle();

    for(int i = 0; i < 2; i++) {
        shapes[i]->draw();
    }

    delete shapes[0];
    delete shapes[1];
    return 0;
}

このコードでは、Shapeクラスに純粋仮想関数drawが定義されており、CircleRectangleクラスでこの関数がオーバーライドされています。

main関数では、Shape型のポインタ配列を使用して、異なる型のオブジェクトに対して同じインターフェイスを呼び出しています。

○サンプルコード4:抽象クラスの実装例

次に、C++で抽象クラスを実装するサンプルコードを紹介します。

ここでは、Animalという抽象クラスと、その派生クラスDogCatを作成します。

#include <iostream>
using namespace std;

// 抽象クラス Animal
class Animal {
public:
    virtual void speak() = 0; // 純粋仮想関数
};

// Animalを継承した派生クラス Dog
class Dog : public Animal {
public:
    void speak() override {
        cout << "ワンワン" << endl;
    }
};

// Animalを継承した派生クラス Cat
class Cat : public Animal {
public:
    void speak() override {
        cout << "ニャーニャー" << endl;
    }
};

int main() {
    Animal* myDog = new Dog();
    Animal* myCat = new Cat();

    myDog->speak();
    myCat->speak();

    delete myDog;
    delete myCat;
    return 0;
}

このコードでは、Animalクラスにspeakという純粋仮想関数が定義されており、DogCatクラスでこの関数が実装されています。

main関数では、Animal型のポインタを使用して、DogCatspeakメソッドを呼び出しています。

●C++クラスと継承の応用例

C++のクラスと継承は、多岐にわたる分野で応用されています。

特に、ゲーム開発やソフトウェア設計では、これらの概念が中心的な役割を果たしています。

○サンプルコード5:ゲーム開発での応用

ゲーム開発では、C++のクラスと継承を利用して、様々なゲーム要素を効率的に管理できます。

例えば、異なるタイプのキャラクターやアイテムを表現するために、共通の基底クラスから派生させたクラスを使うことが一般的です。

ここでは、ゲーム内の異なる種類のキャラクターを表すためのサンプルコードを紹介します。

#include <iostream>
using namespace std;

// 基底クラス Character
class Character {
public:
    virtual void action() = 0;
};

// Characterを継承した派生クラス Warrior
class Warrior : public Character {
public:
    void action() override {
        cout << "戦士が攻撃する" << endl;
    }
};

// Characterを継承した派生クラス Wizard
class Wizard : public Character {
public:
    void action() override {
        cout << "魔法使いが魔法を使う" << endl;
    }
};

int main() {
    Character* myWarrior = new Warrior();
    Character* myWizard = new Wizard();

    myWarrior->action();
    myWizard->action();

    delete myWarrior;
    delete myWizard;
    return 0;
}

このコードでは、Character基底クラスと、その派生クラスWarriorおよびWizardを定義しています。

各クラスはactionメソッドを通して、それぞれのキャラクター特有の行動を表現しています。

○サンプルコード6:ソフトウェア設計での応用

C++のクラスと継承は、ソフトウェア設計においても重要な役割を果たします。

特に、大規模なソフトウェアプロジェクトでは、コードの再利用性と整理が不可欠です。

ここでは、ソフトウェア設計でのクラスと継承の応用例を紹介します。

#include <iostream>
#include <vector>
using namespace std;

// 抽象基底クラス Component
class Component {
public:
    virtual void operate() = 0;
};

// Componentを継承した派生クラス ConcreteComponent
class ConcreteComponent : public Component {
public:
    void operate() override {
        cout << "基本操作" << endl;
    }
};

// Componentを継承した派生クラス Decorator
class Decorator : public Component {
protected:
    Component* component;
public:
    Decorator(Component* c) : component(c) {}
    void operate() override {
        component->operate();
    }
};

// Decoratorを継承した派生クラス EnhancedDecorator
class EnhancedDecorator : public Decorator {
public:
    EnhancedDecorator(Component* c) : Decorator(c) {}
    void operate() override {
        cout << "拡張操作" << endl;
        Decorator::operate();
    }
};

int main() {
    Component* myComponent = new ConcreteComponent();
    Decorator* myDecorator = new EnhancedDecorator(myComponent);

    myDecorator->operate();

    delete myDecorator;
    delete myComponent;
    return 0;
}

この例では、デザインパターンの一つであるデコレーターパターンを実装しています。

Component基底クラスから派生したConcreteComponentDecoratorクラスを使用して、機能を動的に追加しています。

このような設計手法は、ソフトウェアの柔軟性と拡張性を高めるために有効です。

●C++のクラスと継承の注意点と対処法

C++でのクラスと継承を使用する際には、いくつかの重要な注意点があります。

これらのポイントを理解し、適切な対処をすることで、より効率的で安全なプログラミングが可能になります。

○コードの再利用性とメンテナンス

クラスと継承を用いる最大の利点の一つは、コードの再利用性の向上です。

しかし、無計画にクラスを継承すると、コードの複雑さが増し、メンテナンスが困難になることがあります。

継承を使用する際には、下記の点に注意しましょう。

  • 継承は「is-a」の関係が成り立つ場合にのみ使用する
  • コンポジション(組み合わせ)を使う場合も検討する
  • メソッドや変数を派生クラスに公開する前に、必要性を慎重に評価する

○メモリ管理とパフォーマンス

C++では、動的メモリ割り当てと解放が重要な役割を果たします。

クラスを使用する際、特に継承が絡む場合には、メモリリークや未定義動作を避けるために注意が必要です。

ポインタや参照を使用する場合、下記のような対策が有効です。

  • メモリ割り当てを行ったら、必ず適切なタイミングで解放する
  • スマートポインタ(例えば std::unique_ptrstd::shared_ptr)を使用して、自動的なメモリ管理を行う
  • リソースの取得は初期化時に(RAII:Resource Acquisition Is Initialization)することで、例外安全性を高める

また、継承を多用することでパフォーマンスに影響を与える可能性があります。

特に、仮想関数を多用すると実行時のオーバーヘッドが増えるため、必要に応じて非仮想関数を使用することを検討してください。

●C++のクラスと継承のカスタマイズ方法

C++のクラスと継承は、非常に柔軟であり、さまざまな方法でカスタマイズすることができます。

ここでは、ユーザ定義型の拡張やライブラリ及びフレームワークの活用について掘り下げてみましょう。

○ユーザ定義型の拡張

C++では、ユーザ定義型(クラスや構造体)を通じて、データを効率的に管理し操作することができます。

これらの型は、継承を利用して既存の型の機能を拡張したり、新たな機能を加えたりすることができます。

例えば、特定のタイプのリソースを管理するクラスを作成する場合、基本となるクラスにはリソースの取得と解放のロジックを定義し、派生クラスで具体的なリソースタイプに対応した機能を実装することができます。

class Resource {
public:
    virtual void load() = 0;
    virtual void unload() = 0;
};

class Texture : public Resource {
public:
    void load() override {
        // テクスチャを読み込む処理
    }
    void unload() override {
        // テクスチャを解放する処理
    }
};

このように、基本的な機能を提供するクラスを設計し、特定の要件に応じて派生クラスで拡張することで、コードの再利用性と保守性を高めることができます。

○ライブラリとフレームワークの活用

C++で開発を行う際には、様々なライブラリやフレームワークを活用することが一般的です。

これらは、特定の機能やパターンを提供し、開発プロセスを加速します。

例えば、グラフィックス処理にはOpenGLやDirectXのライブラリ、GUI開発にはQtやwxWidgetsのフレームワークがよく使用されます。

これらのツールを利用することで、複雑な処理を抽象化し、より効率的な開発が可能になります。

// Qtフレームワークを使用したGUIアプリケーションの例
#include <QApplication>
#include <QPushButton>

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QPushButton button("Hello, World!");
    button.show();
    return app.exec();
}

この例では、Qtフレームワークを用いて簡単なボタンを持つウィンドウを作成しています。

フレームワークを利用することで、GUIの詳細な実装に関わることなく、直感的なインターフェイスを簡単に構築できます。

まとめ

この記事を通じて、C++のクラスと継承の基本から応用までを深く理解しました。

クラスの定義、継承の概念、ポリモーフィズム、抽象クラス、そしてそれらの応用例と注意点について解説してきました。

これらの知識を駆使することで、初心者から上級者まで、C++のプログラミングスキルを効果的に向上させることができるでしょう。