[C++] クラスの使い方について詳しく解説

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

クラスは、データメンバーとメンバ関数を持つことができ、これによりデータとその操作を一つの単位としてまとめることができます。

クラスの定義は、キーワードclassを用いて行い、アクセス修飾子publicprivateprotectedを使ってメンバーのアクセス制御を行います。

また、コンストラクタやデストラクタを定義することで、オブジェクトの初期化や終了処理をカスタマイズできます。

クラスを使うことで、コードの再利用性や保守性を向上させることが可能です。

この記事でわかること
  • C++のクラスの基本的な構文と機能
  • コンストラクタやデストラクタの役割と使い方
  • 継承とそのアクセス修飾子の使い方
  • 演算子オーバーロードの実装方法
  • テンプレートクラスやデザインパターンの具体例

目次から探す

クラスとは何か

クラスの基本概念

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

クラスは、データ(メンバ変数)とそのデータに対する操作(メンバ関数)をまとめたものです。

クラスを使うことで、現実世界の物体や概念をプログラム内で表現することができます。

以下はクラスの基本的な特徴です。

スクロールできます
特徴説明
カプセル化データとその操作を一つの単位にまとめる
再利用性定義したクラスを使い回すことができる
継承既存のクラスを基に新しいクラスを作成できる

オブジェクト指向プログラミングにおけるクラスの役割

オブジェクト指向プログラミング(OOP)では、クラスは以下のような役割を果たします。

  • データの構造化: クラスを使うことで、関連するデータを一つの単位として扱うことができます。
  • 機能の提供: メンバ関数を通じて、データに対する操作を定義し、クラスの機能を提供します。
  • 抽象化: クラスは、複雑なシステムを単純化し、重要な部分に焦点を当てる手助けをします。

クラスと構造体の違い

C++では、クラスと構造体は似たような機能を持っていますが、いくつかの重要な違いがあります。

以下の表にその違いを示します。

スクロールできます
特徴クラス構造体
デフォルトのアクセス修飾子privatepublic
継承のデフォルトのアクセス修飾子privatepublic
用途複雑なデータ構造や機能を持つ単純なデータ構造を持つ

クラスは主にデータと機能をカプセル化するために使用され、構造体は主にデータの集まりを表現するために使用されます。

クラスの定義と宣言

クラスの基本構文

C++におけるクラスの定義は、classキーワードを使用して行います。

基本的な構文は以下の通りです。

class ClassName {
    // メンバ変数
    // メンバ関数
};

ここで、ClassNameはクラスの名前であり、クラス内にはメンバ変数やメンバ関数を定義します。

クラスを定義した後は、オブジェクトを生成して使用することができます。

メンバ変数とメンバ関数

クラス内には、データを保持するためのメンバ変数と、データを操作するためのメンバ関数を定義します。

以下は、メンバ変数とメンバ関数を持つクラスの例です。

#include <iostream>
using namespace std;
class Car {
public:
    string brand;  // メンバ変数
    int year;      // メンバ変数
    void displayInfo() {  // メンバ関数
        cout << "ブランド: " << brand << ", 年式: " << year << endl;
    }
};

この例では、Carクラスにはbrandyearというメンバ変数があり、displayInfoというメンバ関数が定義されています。

クラスを使ってオブジェクトを生成し、メンバ関数を呼び出すことができます。

int main() {
    Car myCar;          // Carクラスのオブジェクトを生成
    myCar.brand = "トヨタ"; // メンバ変数に値を代入
    myCar.year = 2020;     // メンバ変数に値を代入
    myCar.displayInfo();   // メンバ関数を呼び出す
    return 0;
}
ブランド: トヨタ, 年式: 2020

アクセス修飾子(public, private, protected)

C++では、クラスのメンバに対するアクセス制御を行うために、アクセス修飾子を使用します。

主なアクセス修飾子は以下の通りです。

スクロールできます
アクセス修飾子説明
publicどこからでもアクセス可能
private同じクラス内からのみアクセス可能
protected同じクラスと派生クラスからアクセス可能

これらのアクセス修飾子を使うことで、クラスのデータを保護し、意図しないアクセスを防ぐことができます。

以下は、アクセス修飾子を使用したクラスの例です。

#include <iostream>
using namespace std;
class BankAccount {
private:
    double balance;  // privateメンバ変数
public:
    BankAccount(double initialBalance) { // コンストラクタ
        balance = initialBalance;
    }
    void deposit(double amount) { // publicメンバ関数
        balance += amount;
    }
    void displayBalance() { // publicメンバ関数
        cout << "残高: " << balance << endl;
    }
};

この例では、balanceprivateとして定義されているため、外部から直接アクセスすることはできません。

depositdisplayBalanceといったpublicメンバ関数を通じてのみ、残高を操作することができます。

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

コンストラクタの役割と使い方

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

主な役割は、オブジェクトの初期化を行うことです。

コンストラクタは、クラス名と同じ名前を持ち、戻り値を持ちません。

以下は、コンストラクタの基本的な使い方の例です。

#include <iostream>
using namespace std;
class Rectangle {
public:
    int width, height; // メンバ変数
    // コンストラクタ
    Rectangle(int w, int h) {
        width = w;
        height = h;
    }
    int area() { // 面積を計算するメンバ関数
        return width * height;
    }
};

この例では、Rectangleクラスにコンストラクタが定義されており、オブジェクト生成時に幅と高さを指定して初期化します。

以下のようにオブジェクトを生成し、面積を計算することができます。

int main() {
    Rectangle rect(5, 10); // コンストラクタを呼び出してオブジェクトを生成
    cout << "面積: " << rect.area() << endl; // 面積を表示
    return 0;
}
面積: 50

デストラクタの役割と使い方

デストラクタは、オブジェクトが破棄される際に自動的に呼び出される特別なメンバ関数です。

主な役割は、リソースの解放やクリーンアップを行うことです。

デストラクタは、クラス名の前にチルダ~を付けた名前を持ち、戻り値を持ちません。

以下は、デストラクタの基本的な使い方の例です。

#include <iostream>
using namespace std;
class Sample {
public:
    Sample() { // コンストラクタ
        cout << "オブジェクトが生成されました。" << endl;
    }
    ~Sample() { // デストラクタ
        cout << "オブジェクトが破棄されました。" << endl;
    }
};

この例では、Sampleクラスにコンストラクタとデストラクタが定義されています。

オブジェクトが生成されるとコンストラクタが呼ばれ、破棄されるとデストラクタが呼ばれます。

int main() {
    Sample obj; // オブジェクトを生成
    return 0;   // オブジェクトが破棄される
}
オブジェクトが生成されました。
オブジェクトが破棄されました。

コンストラクタのオーバーロード

コンストラクタのオーバーロードとは、同じクラス内で異なる引数リストを持つ複数のコンストラクタを定義することです。

これにより、異なる方法でオブジェクトを初期化することができます。

以下は、コンストラクタのオーバーロードの例です。

#include <iostream>
using namespace std;
class Circle {
public:
    double radius; // メンバ変数
    // デフォルトコンストラクタ
    Circle() {
        radius = 1.0; // デフォルト値
    }
    // 引数付きコンストラクタ
    Circle(double r) {
        radius = r;
    }
    double area() { // 面積を計算するメンバ関数
        return 3.14 * radius * radius;
    }
};

この例では、Circleクラスにデフォルトコンストラクタと引数付きコンストラクタが定義されています。

以下のように、異なる方法でオブジェクトを生成できます。

int main() {
    Circle circle1;         // デフォルトコンストラクタを呼び出す
    Circle circle2(5.0);   // 引数付きコンストラクタを呼び出す
    cout << "circle1の面積: " << circle1.area() << endl; // デフォルト値の面積
    cout << "circle2の面積: " << circle2.area() << endl; // 指定した半径の面積
    return 0;
}
circle1の面積: 3.14
circle2の面積: 78.5

このように、コンストラクタのオーバーロードを利用することで、柔軟にオブジェクトを初期化することが可能です。

メンバ関数の詳細

メンバ関数の定義と宣言

メンバ関数は、クラス内で定義される関数で、クラスのオブジェクトに関連する操作を実行します。

メンバ関数は、クラスの外部から呼び出すことができ、オブジェクトのメンバ変数にアクセスすることができます。

メンバ関数の基本的な構文は以下の通りです。

class ClassName {
public:
    // メンバ関数の宣言
    void functionName(); 
    // メンバ関数の定義
    void functionName() {
        // 処理内容
    }
};

以下は、メンバ関数を持つクラスの例です。

#include <iostream>
using namespace std;
class Calculator {
public:
    // メンバ関数の宣言
    int add(int a, int b); 
    // メンバ関数の定義
    int add(int a, int b) {
        return a + b;
    }
};

この例では、Calculatorクラスaddというメンバ関数が定義されています。

この関数は、2つの整数を受け取り、その合計を返します。

メンバ関数は、オブジェクトを通じて呼び出すことができます。

int main() {
    Calculator calc; // Calculatorクラスのオブジェクトを生成
    cout << "合計: " << calc.add(3, 4) << endl; // メンバ関数を呼び出す
    return 0;
}
合計: 7

インライン関数

インライン関数は、関数呼び出しのオーバーヘッドを削減するために使用される特別なメンバ関数です。

インライン関数は、inlineキーワードを使用して宣言され、コンパイラは関数の呼び出しをその場で展開します。

以下は、インライン関数の例です。

#include <iostream>
using namespace std;
class Square {
public:
    // インライン関数
    inline int area(int side) {
        return side * side;
    }
};

この例では、Squareクラスにインライン関数areaが定義されています。

この関数は、与えられた辺の長さから面積を計算します。

インライン関数は、通常のメンバ関数と同様に呼び出すことができます。

int main() {
    Square sq; // Squareクラスのオブジェクトを生成
    cout << "面積: " << sq.area(5) << endl; // インライン関数を呼び出す
    return 0;
}
面積: 25

定数メンバ関数

定数メンバ関数は、オブジェクトの状態を変更しないことを保証するために使用されるメンバ関数です。

定数メンバ関数は、関数の宣言の後にconstキーワードを付けて定義します。

これにより、メンバ関数内でメンバ変数を変更することができなくなります。

以下は、定数メンバ関数の例です。

#include <iostream>
using namespace std;
class Point {
private:
    int x, y; // メンバ変数
public:
    Point(int xVal, int yVal) : x(xVal), y(yVal) {} // コンストラクタ
    // 定数メンバ関数
    void display() const {
        cout << "座標: (" << x << ", " << y << ")" << endl;
    }
};

この例では、Pointクラスに定数メンバ関数displayが定義されています。

この関数は、オブジェクトの座標を表示しますが、オブジェクトの状態を変更することはありません。

int main() {
    Point p(3, 4); // Pointクラスのオブジェクトを生成
    p.display();    // 定数メンバ関数を呼び出す
    return 0;
}
座標: (3, 4)

定数メンバ関数を使用することで、オブジェクトの状態を変更しないことを明示的に示すことができ、コードの可読性と安全性が向上します。

クラスの継承

継承の基本概念

継承は、オブジェクト指向プログラミングの重要な概念であり、既存のクラス(基底クラス)から新しいクラス(派生クラス)を作成することを指します。

継承を使用することで、コードの再利用性が向上し、共通の機能を持つクラスを簡単に作成できます。

派生クラスは、基底クラスのメンバ変数やメンバ関数を引き継ぎ、さらに独自のメンバを追加することができます。

基底クラスと派生クラス

基底クラスは、他のクラスが継承するためのクラスであり、派生クラスは基底クラスを拡張したクラスです。

以下は、基底クラスと派生クラスの例です。

#include <iostream>
using namespace std;
// 基底クラス
class Animal {
public:
    void speak() {
        cout << "動物の声" << endl;
    }
};
// 派生クラス
class Dog : public Animal {
public:
    void bark() {
        cout << "ワンワン" << endl;
    }
};

この例では、Animalクラスが基底クラスで、Dogクラスがその派生クラスです。

DogクラスAnimalクラス speakメンバ関数を継承しています。

int main() {
    Dog myDog; // Dogクラスのオブジェクトを生成
    myDog.speak(); // 基底クラスのメンバ関数を呼び出す
    myDog.bark();  // 派生クラスのメンバ関数を呼び出す
    return 0;
}
動物の声
ワンワン

アクセス修飾子と継承

継承において、アクセス修飾子は基底クラスのメンバに対するアクセスの可否を決定します。

C++では、継承の際にpublicprotectedprivateのいずれかのアクセス修飾子を指定できます。

以下の表に、各修飾子の影響を示します。

スクロールできます
アクセス修飾子基底クラスのメンバへのアクセス
public派生クラスからもアクセス可能
protected派生クラスからアクセス可能だが、外部からはアクセス不可
private派生クラスからはアクセス不可

以下は、アクセス修飾子を使用した継承の例です。

#include <iostream>
using namespace std;
class Base {
protected:
    int protectedVar; // protectedメンバ変数
public:
    Base() : protectedVar(10) {} // コンストラクタ
};
class Derived : public Base {
public:
    void show() {
        cout << "protectedVar: " << protectedVar << endl; // 派生クラスからアクセス可能
    }
};
int main() {
    Derived obj; // Derivedクラスのオブジェクトを生成
    obj.show();  // 派生クラスのメンバ関数を呼び出す
    return 0;
}
protectedVar: 10

仮想関数と多態性

仮想関数は、基底クラスで定義され、派生クラスでオーバーライドされる関数です。

仮想関数を使用することで、多態性を実現し、同じインターフェースで異なる動作を持つオブジェクトを扱うことができます。

仮想関数は、基底クラスでvirtualキーワードを使用して宣言します。

以下は、仮想関数の例です。

#include <iostream>
using namespace std;
class Shape {
public:
    virtual void draw() { // 仮想関数
        cout << "形状を描画" << endl;
    }
};
class Circle : public Shape {
public:
    void draw() override { // オーバーライド
        cout << "円を描画" << endl;
    }
};
class Square : public Shape {
public:
    void draw() override { // オーバーライド
        cout << "四角を描画" << endl;
    }
};

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

以下のように、基底クラスのポインタを使用して派生クラスのオブジェクトを扱うことができます。

int main() {
    Shape* shape1 = new Circle(); // Circleオブジェクトを生成
    Shape* shape2 = new Square(); // Squareオブジェクトを生成
    shape1->draw(); // Circleのdrawを呼び出す
    shape2->draw(); // Squareのdrawを呼び出す
    delete shape1; // メモリを解放
    delete shape2; // メモリを解放
    return 0;
}
円を描画
四角を描画

このように、仮想関数を使用することで、基底クラスのポインタを通じて異なる派生クラスのメンバ関数を呼び出すことができ、多態性を実現できます。

これにより、柔軟で拡張性のあるプログラムを構築することが可能になります。

演算子のオーバーロード

演算子オーバーロードの基本

演算子オーバーロードは、C++の機能の一つで、既存の演算子に対して新しい意味を持たせることができる機能です。

これにより、ユーザー定義の型(クラス)に対して、演算子を使った直感的な操作が可能になります。

演算子オーバーロードは、通常の関数と同様に、特定の演算子に対して関数を定義することで実現されます。

以下は、演算子オーバーロードの基本的な構文です。

ReturnType operator演算子名(引数リスト) {
    // 処理内容
}

例えば、+演算子をオーバーロードして、2つのオブジェクトを加算するクラスを作成することができます。

以下はその例です。

#include <iostream>
using namespace std;
class Point {
public:
    int x, y; // メンバ変数
    Point(int xVal, int yVal) : x(xVal), y(yVal) {} // コンストラクタ
    // +演算子のオーバーロード
    Point operator+(const Point& p) {
        return Point(x + p.x, y + p.y);
    }
};

この例では、Pointクラス+演算子がオーバーロードされています。

2つのPointオブジェクトを加算すると、新しいPointオブジェクトが生成されます。

int main() {
    Point p1(1, 2);
    Point p2(3, 4);
    Point p3 = p1 + p2; // オーバーロードされた+演算子を使用
    cout << "p3の座標: (" << p3.x << ", " << p3.y << ")" << endl; // 結果を表示
    return 0;
}
p3の座標: (4, 6)

メンバ関数としてのオーバーロード

演算子オーバーロードは、メンバ関数として定義することができます。

この場合、左オペランドはオブジェクト自身になります。

以下は、メンバ関数として-演算子をオーバーロードする例です。

#include <iostream>
using namespace std;
class Vector {
public:
    int x, y; // メンバ変数
    Vector(int xVal, int yVal) : x(xVal), y(yVal) {} // コンストラクタ
    // -演算子のオーバーロード
    Vector operator-(const Vector& v) {
        return Vector(x - v.x, y - v.y);
    }
};

この例では、Vectorクラス-演算子がオーバーロードされています。

2つのVectorオブジェクトを減算すると、新しいVectorオブジェクトが生成されます。

int main() {
    Vector v1(5, 10);
    Vector v2(2, 3);
    Vector v3 = v1 - v2; // オーバーロードされた-演算子を使用
    cout << "v3の座標: (" << v3.x << ", " << v3.y << ")" << endl; // 結果を表示
    return 0;
}
v3の座標: (3, 7)

フレンド関数としてのオーバーロード

演算子オーバーロードは、フレンド関数としても定義できます。

この場合、演算子の左オペランドはクラスの外部にあり、フレンド関数はそのクラスのプライベートメンバにアクセスできます。

以下は、フレンド関数として*演算子をオーバーロードする例です。

#include <iostream>
using namespace std;
class Matrix {
public:
    int a, b, c, d; // メンバ変数
    Matrix(int aVal, int bVal, int cVal, int dVal) : a(aVal), b(bVal), c(cVal), d(dVal) {} // コンストラクタ
    // *演算子のフレンド関数としてのオーバーロード
    friend Matrix operator*(const Matrix& m1, const Matrix& m2) {
        return Matrix(
            m1.a * m2.a + m1.b * m2.c,
            m1.a * m2.b + m1.b * m2.d,
            m1.c * m2.a + m1.d * m2.c,
            m1.c * m2.b + m1.d * m2.d
        );
    }
};

この例では、Matrixクラス*演算子がフレンド関数としてオーバーロードされています。

2つのMatrixオブジェクトを掛け算すると、新しいMatrixオブジェクトが生成されます。

int main() {
    Matrix m1(1, 2, 3, 4);
    Matrix m2(5, 6, 7, 8);
    Matrix m3 = m1 * m2; // オーバーロードされた*演算子を使用
    cout << "m3の要素: (" << m3.a << ", " << m3.b << ", " << m3.c << ", " << m3.d << ")" << endl; // 結果を表示
    return 0;
}
m3の要素: (19, 22, 43, 50)

このように、演算子オーバーロードを使用することで、ユーザー定義の型に対して直感的な操作を実現することができます。

メンバ関数としてのオーバーロードとフレンド関数としてのオーバーロードの使い分けにより、柔軟な設計が可能になります。

テンプレートクラス

テンプレートクラスの基本

テンプレートクラスは、C++におけるジェネリックプログラミングの機能であり、型に依存しないクラスを定義することができます。

これにより、異なるデータ型に対して同じクラスのロジックを再利用することが可能になります。

テンプレートクラスは、templateキーワードを使用して定義され、型パラメータを指定します。

以下は、テンプレートクラスの基本的な構文です。

template <typename T>
class ClassName {
public:
    T memberVariable; // メンバ変数
    void setValue(T value) { // メンバ関数
        memberVariable = value;
    }
    T getValue() {
        return memberVariable;
    }
};

テンプレートクラスの定義と使用

テンプレートクラスを定義した後、特定の型を指定してインスタンスを生成することができます。

以下は、テンプレートクラスを使用した例です。

#include <iostream>
using namespace std;
// テンプレートクラスの定義
template <typename T>
class Box {
public:
    T value; // メンバ変数
    Box(T val) : value(val) {} // コンストラクタ
    T getValue() {
        return value;
    }
};
int main() {
    Box<int> intBox(123); // int型のBoxを生成
    Box<double> doubleBox(45.67); // double型のBoxを生成
    cout << "intBoxの値: " << intBox.getValue() << endl; // int型の値を表示
    cout << "doubleBoxの値: " << doubleBox.getValue() << endl; // double型の値を表示
    return 0;
}
intBoxの値: 123
doubleBoxの値: 45.67

この例では、Boxというテンプレートクラスを定義し、int型double型のインスタンスを生成しています。

テンプレートクラスを使用することで、同じクラスのロジックを異なる型に対して再利用できます。

テンプレートクラスの特殊化

テンプレートクラスの特殊化は、特定の型に対して異なる実装を提供するための機能です。

これにより、特定の型に対して最適化された動作を実現できます。

特殊化には、完全特殊化と部分特殊化の2種類があります。

以下は、完全特殊化の例です。

#include <iostream>
using namespace std;
// テンプレートクラスの定義
template <typename T>
class Printer {
public:
    void print(T value) {
        cout << "値: " << value << endl;
    }
};
// 完全特殊化
template <>
class Printer<int> { // int型に対する特殊化
public:
    void print(int value) {
        cout << "整数値: " << value << endl;
    }
};
int main() {
    Printer<double> doublePrinter; // double型のPrinter
    doublePrinter.print(3.14); // 通常の動作
    Printer<int> intPrinter; // int型のPrinter
    intPrinter.print(42); // 特殊化された動作
    return 0;
}
値: 3.14
整数値: 42

この例では、Printerというテンプレートクラスを定義し、int型に対して完全特殊化を行っています。

int型Printerは、特別なメッセージを表示するように実装されています。

これにより、特定の型に対して異なる動作を実現することができます。

テンプレートクラスを使用することで、柔軟で再利用可能なコードを作成し、特定の型に対して最適化された実装を提供することが可能になります。

応用例

スマートポインタの実装

スマートポインタは、C++においてメモリ管理を簡素化し、メモリリークを防ぐためのクラスです。

std::unique_ptrstd::shared_ptrなどのスマートポインタが標準ライブラリに含まれていますが、ここでは簡単な独自のスマートポインタを実装してみます。

#include <iostream>
using namespace std;
template <typename T>
class SmartPointer {
private:
    T* ptr; // ポインタ
public:
    // コンストラクタ
    SmartPointer(T* p = nullptr) : ptr(p) {}
    // デストラクタ
    ~SmartPointer() {
        delete ptr; // メモリを解放
    }
    // オペレーターのオーバーロード
    T& operator*() {
        return *ptr; // ポインタの参照を返す
    }
    T* operator->() {
        return ptr; // ポインタを返す
    }
};

このSmartPointerクラスは、ポインタを管理し、デストラクタで自動的にメモリを解放します。

以下は、スマートポインタを使用する例です。

int main() {
    SmartPointer<int> sp(new int(10)); // スマートポインタを生成
    cout << "値: " << *sp << endl; // 値を表示
    return 0;
}
値: 10

シングルトンパターンの実装

シングルトンパターンは、クラスのインスタンスが1つだけであることを保証し、そのインスタンスへのグローバルなアクセスを提供するデザインパターンです。

以下は、シングルトンパターンの実装例です。

#include <iostream>
using namespace std;
class Singleton {
private:
    static Singleton* instance; // インスタンスを保持する静的ポインタ
    // コンストラクタをプライベートにする
    Singleton() {}
public:
    // インスタンスを取得するための静的メソッド
    static Singleton* getInstance() {
        if (!instance) {
            instance = new Singleton(); // インスタンスが存在しない場合に生成
        }
        return instance;
    }
    void showMessage() {
        cout << "シングルトンインスタンスにアクセスしました。" << endl;
    }
};
// 静的メンバの初期化
Singleton* Singleton::instance = nullptr;

このSingletonクラスは、インスタンスを1つだけ持つことを保証します。

以下は、シングルトンインスタンスを使用する例です。

int main() {
    Singleton* s1 = Singleton::getInstance(); // インスタンスを取得
    s1->showMessage(); // メッセージを表示
    Singleton* s2 = Singleton::getInstance(); // 再度インスタンスを取得
    s2->showMessage(); // メッセージを表示
    return 0;
}
シングルトンインスタンスにアクセスしました。
シングルトンインスタンスにアクセスしました。

クラスを使ったデザインパターンの例

デザインパターンは、ソフトウェア設計における一般的な解決策を提供します。

ここでは、ファクトリーパターンの例を示します。

ファクトリーパターンは、オブジェクトの生成をカプセル化し、クライアントコードからの依存を減らします。

#include <iostream>
using namespace std;
// 製品のインターフェース
class Product {
public:
    virtual void use() = 0; // 純粋仮想関数
};
// 具体的な製品A
class ProductA : public Product {
public:
    void use() override {
        cout << "製品Aを使用しています。" << endl;
    }
};
// 具体的な製品B
class ProductB : public Product {
public:
    void use() override {
        cout << "製品Bを使用しています。" << endl;
    }
};
// ファクトリークラス
class Factory {
public:
    static Product* createProduct(const string& type) {
        if (type == "A") {
            return new ProductA(); // 製品Aを生成
        } else if (type == "B") {
            return new ProductB(); // 製品Bを生成
        }
        return nullptr; // 無効なタイプ
    }
};

この例では、Productインターフェースとその具体的な実装であるProductAProductBを定義し、Factoryクラスで製品を生成します。

以下は、ファクトリーパターンを使用する例です。

int main() {
    Product* product1 = Factory::createProduct("A"); // 製品Aを生成
    product1->use(); // 製品Aを使用
    Product* product2 = Factory::createProduct("B"); // 製品Bを生成
    product2->use(); // 製品Bを使用
    delete product1; // メモリを解放
    delete product2; // メモリを解放
    return 0;
}
製品Aを使用しています。
製品Bを使用しています。

このように、クラスを使ったデザインパターンを実装することで、コードの再利用性や可読性を向上させることができます。

デザインパターンは、特定の問題に対する効果的な解決策を提供し、ソフトウェア開発のベストプラクティスを促進します。

よくある質問

クラスと構造体の使い分けは?

クラスと構造体は、どちらもデータをまとめるための構造体ですが、主な違いはデフォルトのアクセス修飾子です。

クラスはデフォルトでprivateであり、構造体はデフォルトでpublicです。

一般的に、クラスはデータと機能をカプセル化するために使用され、構造体は単純なデータの集まりを表現するために使用されます。

クラスはオブジェクト指向プログラミングの概念に基づいているため、より複雑なデータ構造や機能を持つ場合に適しています。

コンストラクタで初期化リストを使う理由は?

コンストラクタで初期化リストを使用する理由は、メンバ変数を効率的に初期化するためです。

初期化リストを使用すると、メンバ変数がコンストラクタの本体に入る前に初期化されるため、特にconstや参照型のメンバ変数を持つクラスでは必須です。

また、初期化リストを使用することで、オブジェクトの初期化がより効率的になり、パフォーマンスが向上します。

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

継承とコンポジションは、オブジェクト指向プログラミングにおける2つの異なる関係を表します。

継承は、基底クラスから派生クラスを作成することで、親子関係を形成します。

これにより、派生クラスは基底クラスのメンバを引き継ぎます。

一方、コンポジションは、クラスが他のクラスのインスタンスをメンバとして持つことで、”has-a”関係を形成します。

コンポジションは、柔軟性が高く、クラスの再利用性を向上させるため、一般的には継承よりも好まれることが多いです。

まとめ

この記事では、C++のクラスの使い方について詳しく解説しました。

クラスの基本概念から、継承、演算子オーバーロード、テンプレートクラス、デザインパターンの実装例まで、幅広くカバーしました。

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

ぜひ、実際のプロジェクトでクラスの機能を活用してみてください。

当サイトはリンクフリーです。出典元を明記していただければ、ご自由に引用していただいて構いません。

関連カテゴリーから探す

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