第24回 クラス(class と struct)
C の struct は「データをまとめる箱」でした。C++ のクラスはそれに『関数(メンバ関数)』と『アクセス制御』を加えたもの。データと、そのデータを操作する手続きを 1 つの型にまとめます。class と struct の違いはたった 1 つ(既定のアクセス制御)だけ。ここから C++ の本格的なオブジェクト指向が始まります。
このページで押さえること
✅ 最低限ここだけ覚える
- クラスはデータ + それを扱う関数をまとめた型
class と struct の違いは既定のアクセス制御だけ
public: は外から、private: は自分だけ
- C++ の struct は C とは違ってメンバ関数が書ける
⭐ 余裕があれば読む
- カプセル化の意義
- getter / setter の書き方
protected:(継承で使う)
- 前方宣言と pimpl(実装隠蔽)
1. まず触ってみる ― Point クラスを作る
クラスは「データと、それを扱う関数」を 1 つの型にまとめた設計図です。たとえば「2D 座標」を表すクラスなら、データは x, y、関数は「距離を計算する」など。
class Point
privateint x_, y_;← データ(非公開)
publicPoint(int x, int y);← 作り方(次回)
publicint get_x() const;← 読み取り
publicint get_y() const;
publicdouble distance_to(Point p) const;← 計算
外からのアクセス:
Point p(3, 4);
p.get_x(); → OK(public)
p.distance_to(q); → OK(public)
p.x_; → エラー(private)
まずは 10 行だけ
first_class.cppC++ 最小例
class Counter {
private:
int count_ = 0; // ← データ(private)
public:
void increment() { ++count_; } // ← メンバ関数
int value() const { return count_; }
};
int main() {
Counter c; // オブジェクトを作る
c.increment(); // 関数を呼ぶ(ドットで)
c.increment();
std::cout << c.value(); // 2
}
ここまでで覚えること(3 つ):
- クラスの中にデータと関数を両方書ける
private: / public: でアクセス制御
- オブジェクトの操作は
obj.method()(C の構造体と同じドット)
よくある素朴な疑問
Q. C の struct と似ているけど何が違う?
→ C++ の struct にはメンバ関数・継承・アクセス制御が全部あります。C++ では struct と class はほぼ同じもので、違いは既定のアクセス制御だけ(struct は public、class は private)。詳しくは §4 で。
Q. なぜ private が必要?
→ 「変更してほしくないデータを勝手に変更されるのを防ぐ」ため。カプセル化と呼びます。詳しくは §5 で。
Q. 語尾の _(アンダースコア)って何?
→ 慣習。「これはメンバ変数ですよ」と目印をつける命名ルール。count_ / x_ / y_ のように末尾に付けるスタイルが Google 等で推奨。m_count のように接頭辞を付ける流派もあります。
2. C の struct との違い
C の struct を使っていた感覚からすると、C++ のクラスは自然な拡張です。
CC
#include <stdio.h>
struct Counter {
int count; // データだけ
};
// 関数は外に書く
void increment(struct Counter* c) {
c->count++;
}
int main(void) {
struct Counter c = {0};
increment(&c);
printf("%d\n", c.count);
}
C++C++
#include <iostream>
class Counter {
public:
int count = 0;
void increment() { count++; } // メンバ関数
};
int main() {
Counter c;
c.increment(); // c.関数() で呼ぶ
std::cout << c.count;
}
変わったポイント
- 関数がクラスの中に入る:「Counter に対して何ができるか」が一目で分かる
struct キーワードが不要:C のように struct Counter と書かず Counter だけで OK
- メンバアクセスは
. or ->:C と同じ
- this ポインタ:メンバ関数内では自分自身を指す(詳細は第 26 回)
C の関数が「型 + 関数名」の命名規則を強いられた理由: C には関数オーバーロード(第 10 回)もないので、同じ名前の関数が作れません。counter_increment(), player_move() のように「型名_動作」と命名するのが慣習でした。C++ のクラスなら全部 increment() / move() で OK。どの型のメソッドかは呼び出し側が明示(c.increment())するので曖昧にならない。
3. public / private ― アクセス制御
クラスの中身を「公開する/しない」を区別します。外からクラスを使う側にとっては、public: がAPI(使える操作)、private: が実装の詳細(触っちゃダメ)。
アクセス指定子基本
class BankAccount {
private:
int balance_ = 0; // 外からは触れない
public:
void deposit(int amount) {
balance_ += amount; // 自分のメンバ関数からはOK
}
int balance() const {
return balance_;
}
};
BankAccount a;
a.deposit(100); // OK (public)
a.balance(); // OK (public) → 100
// a.balance_ = 999; ← エラー (private)
3 種類全体像
class Foo {
private:
// 自分のメンバ関数からだけ
int secret_;
public:
// 外の誰からもアクセス可
void api();
protected:
// 自分と派生クラスから可
// (継承で使う、第 43 回で扱う)
int shared_;
};
getter / setter のパターン
「データは private で守り、public の関数を通してのみ読み書きさせる」のが典型パターン。
getter / setter基本パターン
class User {
private:
std::string name_;
int age_ = 0;
public:
// getter(読み取り)
std::string name() const { return name_; }
int age() const { return age_; }
// setter(書き込み、チェック付き)
void set_age(int a) {
if (a >= 0) age_ = a; // 不正値を弾ける
}
};
getter / setter は万能薬ではない: 単に private を介して素通しするだけの getter / setter は、結局データを外に晒しているだけで意味が薄い(public と大差ない)。「値の正当性チェック」「計算して返す」「書き込み時の処理」など、その関数に意味があるときだけ作るのが上級者の流儀。「全メンバに getter / setter」は非推奨。
4. class vs struct ― 唯一の違い
よく聞かれる質問「class と struct はどう違うんですか?」の答え:
既定のアクセス制御が違うだけ。
class は既定が private、struct は既定が public。
それ以外はまったく同じ。
class既定 private
class A {
int x; // 既定は private
public:
int y;
};
A a;
// a.x = 1; ← エラー
a.y = 2; // OK
struct既定 public
struct B {
int x; // 既定は public
private:
int y;
};
B b;
b.x = 1; // OK
// b.y = 2; ← エラー
慣習として使い分ける
同じものなので機能的にはどちらを使っても OK ですが、慣習として:
struct:メンバ変数だけの単純なデータの塊(C の struct のような使い方)。全部 public で済むもの
class:データ + メンバ関数 + 不変条件(invariant)を持つ本格的なクラス
struct が自然データの塊
struct Point { double x, y; };
struct Color { int r, g, b; };
struct HttpResponse {
int status;
std::string body;
};
// 全部 public で十分
// std::pair も実質こういう使い方
class が自然本格クラス
class BankAccount {
private:
int balance_ = 0; // 不正値禁止
public:
void deposit(int);
void withdraw(int);
int balance() const;
};
// 外からの直接操作を制限したい
まとめ: struct と class の違いは『書いている人の意図の表明』に近い。機能は同じなので、「メンバが全部 public でいいな」と思ったら struct、「実装を隠蔽したいな」と思ったら class。
⭐
ここまでで日常は OK
残りは「なぜ private にするのか」という設計の話。クラス設計の根幹なので、興味があれば。
5. カプセル化 ― なぜ private が必要か
単にデータをまとめるだけなら struct で十分。わざわざ private で隠す理由は 3 つあります。
理由 1: 不変条件(invariant)を守るため
「年齢は 0 以上」「残高はマイナスにならない」のようなルールを、クラスの内側で常に守る設計ができます。
struct だと破綻
struct User { int age; };
User u;
u.age = -999; // 誰でも壊せる
class で守る安全
class User {
int age_ = 0;
public:
void set_age(int a) {
if (a < 0) throw std::invalid_argument("...");
age_ = a;
}
};
理由 2: 内部実装を変えやすくするため
たとえば「温度を摂氏で保持するか華氏で保持するか」を後で変更したくても、public の API が変わらなければ利用者には影響ゼロ。
版 1(内部 摂氏)v1
class Temperature {
double celsius_;
public:
double in_celsius() const { return celsius_; }
double in_fahrenheit() const { return celsius_ * 9/5 + 32; }
};
版 2(内部 ケルビン)v2
class Temperature {
double kelvin_; // 実装を変えた
public:
double in_celsius() const { return kelvin_ - 273.15; }
double in_fahrenheit() const { return (kelvin_ - 273.15) * 9/5 + 32; }
};
// 利用者は全く修正不要
理由 3: 副作用を明確にするため
「変数への書き込み」がメンバ関数の中でしか起きないことが保証されると、デバッグの時に「誰が値を変えたか」を追う範囲が狭まる。これはバグ追跡に絶大な効果があります。
まとめ: private は「他人に使われない」ことが目的ではなく、「クラスの責任範囲を明確にする」ための道具。public の関数が「このクラスがやってくれること」、private のメンバが「このクラスが内側で持っているもの」。
確認クイズ
クラス基本を 4 問で確認。
Q1. class と struct の違いとして正しいのは?
class は継承できるが struct はできない
class はメンバ関数を持てるが struct は持てない
既定のアクセス制御が違うだけ(class は private、struct は public)
class はヒープに、struct はスタックに確保される
C++ では class と struct はほぼ同じ機能。唯一の違いは既定のアクセス制御。どちらもメンバ関数を持て、継承でき、ヒープにもスタックにも置けます。慣習として「単純なデータの塊には struct」「内部を隠蔽したい型は class」と使い分けます。
Q2. class Foo { int x; }; で作った Foo のインスタンス f から f.x にアクセスできる?
できる
できない(class の既定は private)
警告は出るがアクセスできる
x が 0 で初期化されているならできる
class はアクセス指定子を書かないと既定で private。外から f.x は触れません。struct Foo { int x; }; なら既定 public で触れます。
Q3. private なメンバにアクセスできるのは?
何でも(実質 public と同じ)
同じファイル内のコード
そのクラスのメンバ関数と friend 宣言された関数・クラス
そのクラスの派生クラスだけ
private は「そのクラスの内部」でしかアクセスできません。派生クラスからアクセスさせたければ protected:(第 42 回で扱う)、特定の外部関数にアクセスさせたければ friend 宣言を使います。
Q4. 「xyz 座標を単に保持するだけ」のシンプルな型を作るとき、最も自然な書き方は?
class Point { int x, y, z; };
struct Point { int x, y, z; };
class Point { public: int x, y, z; private: };
std::vector<int> point; // 3 要素
データだけを持つ集約型はstruct が慣習。すべて public でよく、意図が明確。①は class なので全部 private になり使い物にならない。③は正しく動くが冗長。④はそもそも型の名前が付かない。