C++におけるクラスとオブジェクトの設計と実装

C++は、C言語の「手続き型」のアプローチに対し、「オブジェクト指向」を基盤とした言語です。オブジェクト指向では、複雑な処理を独立したオブジェクト間の相互作用として捉えます。C++における構造体(struct)は、C言語との互換性を保ちつつ、メンバ関数を持つことができる「クラス」へと拡張されています。

クラスの定義とアクセス制御

C++では、classまたはstructキーワードを使用して型を定義します。これらはメンバ変数(プロパティ)とメンバ関数(メソッド)を内包します。

  • クラス名: それ自体が新しい型名として機能します。
  • メンバ関数: クラス内で定義された場合、インライン関数として処理される可能性があります。宣言と定義を分離する場合は、スコープ解決演算子(::)を使用します。

アクセス修飾子

クラス内部の情報のカプセル化を制御するために、以下の3つの修飾子が用意されています。

  • public: クラス外部から自由にアクセス可能。
  • private: クラス内部からのみアクセス可能。classのデフォルト設定。
  • protected: クラス内部および派生クラスからアクセス可能。

オブジェクトのインスタンス化とメモリ構造

クラスから実体を作成することを「インスタンス化」と呼びます。インスタンス化されたオブジェクトは、物理的なメモリ領域を占有します。特筆すべき点として、メンバ変数を持たない空のクラスであっても、その存在を識別するために1バイトのメモリが割り当てられます。

メモリ配分とアライメント

C++では、メモリへのアクセス効率を最適化するために「メモリライメント」が行われます。オブジェクト内にはメンバ変数のみが格納され、メンバ関数は共通のコードセグメントに保持されます。アライメントの計算ルールは以下の通りです。

  1. 最初のメンバはオフセット0に配置。
  2. 以降のメンバは、その型サイズまたはコンパイラのデフォルト値(VSでは通常8)のうち、小さい方の倍数のアドレスに配置。
  3. 全体のサイズは、最大のアライメント定数の倍数になるようパディングされる。

thisポインタの役割

非静的メンバ関数には、呼び出し元オブジェクトを指す隠しパラメータthisポインタが存在します。これはType* const this型として扱われ、関数内でメンバにアクセスする際に使用されます。通常、レジスタ(ECXなど)を介して効率的に渡されます。

6つの特殊メンバ関数

明示的に記述しなくても、コンパイラは必要に応じて以下の関数を自動生成します。

1. コンストラクタ

オブジェクトの初期化を担います。戻り値を持たず、クラス名と同名で定義します。explicitキーワードを付与することで、意図しない暗黙の型変換を抑制できます。

2. デストラクタ

オブジェクトの寿命が尽きた際のクリーンアップ(メモリ解放など)を行います。クラス名の前に~を付けます。

3. コピーコンストラクタ

既存のオブジェクトを元に新しいオブジェクトを生成します。引数は必ず参照渡し(const ClassName&)にする必要があります。

4. 代入演算子オーバーロード

既存のオブジェクトに別のオブジェクトの値を代入します。ClassName& operator=(const ClassName& obj)の形式で、連続代入をサポートするために自身の参照を返却するのが一般的です。

5. & 6. アドレス演算子のオーバーロード

オブジェクトのアドレスを取得する際の挙動を定義しますが、通常はデフォルトのままで問題ありません。

初期化リスト

コンストラクタの本体が実行される前に、メンバを直接初期化する仕組みです。特にconstメンバや参照メンバ、デフォルトコンストラクタのないユーザ定義型を含む場合は、初期化リストの使用が必須となります。

class DataHandler {
public:
    DataHandler(int val, int& externalRef) 
        : _value(val), _ref(externalRef), _constVal(100) {
        // コンストラクタ本体
    }
private:
    int _value;
    int& _ref;
    const int _constVal;
};

staticメンバの特性

static修飾子を付けたメンバは、特定のインスタンスではなくクラス全体に帰属します。

  • 静的メンバ変数: クラス外で実体を定義・初期化する必要があります。
  • 静的メンバ関数: thisポインタを持たないため、インスタンス変数を直接参照することはできません。クラス名から直接呼び出し可能です。
class Counter {
public:
    Counter() { ++_count; }
    static int GetTotal() { return _count; }
private:
    static int _count; // 宣言
};

int Counter::_count = 0; // クラス外での定義

フレンド(friend)と内部クラス

カプセル化の原則を一時的にバイパスする手段として、friendキーワードが存在します。

  • フレンド関数/クラス: 指定された外部の関数やクラスから、privateメンバへの直接アクセスを許可します。ただし、密結合を招くため多用は避けるべきです。
  • 内部クラス: あるクラスの中で定義されたクラス。外部クラスのフレンドとして扱われますが、メモリサイズ的には独立しており、外部クラスのサイズに影響を与えません。

タグ: cpp ObjectOriented MemoryManagement ProgrammingConcepts

9月1日 11:53 投稿