クラスは、データ(属性)とそのデータを操作する関数(メソッド)を一つの単位としてまとめる仕組みです。この統合により、現実世界の概念をコード上で自然に表現できます。
アクセス制御
メンバーには public、private、protected のアクセス修飾子を指定でき、外部からのアクセス範囲を制御します。friend 宣言された関数やクラスは、private や protected メンバーにもアクセス可能です。
インスタンス化
クラスから具体的なオブジェクトを作成することを「インスタンス化」と呼びます。スタック上での宣言や、new を使ったヒープ領域への動的確保が可能です。ただし、ポインタや参照の宣言だけではインスタンス化とは見なされません。
コンストラクタとデストラクタ
コンストラクタはオブジェクト生成時に一度だけ呼び出され、初期化処理を担います。デストラクタはスコープ終了時に自動で呼び出され、リソースの解放を行います。
デフォルト引数を持つコンストラクタは、呼び出し時に引数を渡すか否かで、有参または無参として扱われます。
class Human {
public:
Human(int age = 25) : age_(age) {}
int age_;
};
int main() {
Human h1(30); // 有参 → age_ = 30
Human h2; // 無参 → age_ = 25
}
コピーコンストラクタ
オブジェクトをコピーして新たなオブジェクトを生成する際に呼び出されます。ユーザーが定義しない場合、コンパイラは各メンバーを値コピーするデフォルトのコピーコンストラクタを提供します。
class Point {
public:
int x;
Point(const Point& other) : x(other.x) {
std::cout << "Copy constructor called\n";
}
};
コピーコンストラクタは以下の場合に呼び出されます:
- オブジェクトを値渡しで関数に渡すとき
- 戻り値としてオブジェクトを返すとき
- 明示的にコピー初期化を行うとき(例:
Point p2(p1);)
浅いコピーと深いコピー
メンバーにポインタが含まれる場合、デフォルトのコピーコンストラクタによる「浅いコピー」では、複数のオブジェクトが同一のヒープ領域を指すことになり、デストラクタで二重解放が発生する危険があります。
これを避けるためには、「深いコピー」を実装し、ヒープ領域を新たに確保する必要があります。
class Person {
public:
int* height_;
Person(int h) : height_(new int(h)) {}
// 深いコピーのコピーコンストラクタ
Person(const Person& other) : height_(new int(*other.height_)) {}
~Person() {
delete height_;
height_ = nullptr;
}
// 深いコピーの代入演算子
Person& operator=(const Person& other) {
if (this != &other) {
delete height_;
height_ = new int(*other.height_);
}
return *this;
}
};
静的メンバ
static メンバ変数はクラス全体で共有され、グローバル領域に格納されます。クラス内では宣言のみを行い、クラス外で定義・初期化します。
class Counter {
public:
static int count;
};
int Counter::count = 0;
静的メンバ関数は this ポインタを持たず、非静的メンバにアクセスできません。ただし、非静的メンバからは静的メンバにアクセス可能です。
this ポインタ
非静的メンバ関数内では、暗黙のうちに this ポインタが存在し、呼び出し元のオブジェクトを指します。これにより、同名の引数とメンバ変数を区別したり、メソッドチェーンを実現できます。
class Accumulator {
int value_ = 0;
public:
Accumulator& add(int n) {
value_ += n;
return *this; // this を返すことでチェーン可能
}
};
const メンバ関数と const オブジェクト
メンバ関数の末尾に const を付けると、その関数内ではメンバ変数を変更できません(mutable 修飾子付きの変数を除く)。このような関数を「const メンバ関数」と呼びます。
const オブジェクトは、const メンバ関数のみ呼び出せます。これは、オブジェクトの状態が不変であることを保証するためです。
空ポインタとメンバ関数呼び出し
メンバ関数がメンバ変数にアクセスしない場合(例: 単なる出力)、空ポインタ経由でも呼び出せることがあります。しかし、メンバ変数にアクセスする場合は未定義動作となるため、事前にヌルチェックが必要です。
コンパイラが自動生成する関数
ユーザーが何も定義しない場合、C++ コンパイラは以下の3つの関数を自動生成します:
- デフォルトコンストラクタ(引数なし、空の本体)
- デフォルトデストラクタ
- コピーコンストラクタ(メンバーごとの値コピー)
ただし、ユーザーが有参コンストラクタを定義すると、デフォルトコンストラクタは生成されなくなります。コピーコンストラクタを自分で定義した場合、他のコンストラクタは生成されません。