C++ Learning

第24回 クラス(class と struct)

C の struct は「データをまとめる箱」でした。C++ のクラスはそれに『関数(メンバ関数)』と『アクセス制御』を加えたもの。データと、そのデータを操作する手続きを 1 つの型にまとめます。classstruct の違いはたった 1 つ(既定のアクセス制御)だけ。ここから C++ の本格的なオブジェクト指向が始まります。

このページで押さえること
✅ 最低限ここだけ覚える
  • クラスはデータ + それを扱う関数をまとめた型
  • classstruct の違いは既定のアクセス制御だけ
  • 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++ では structclassほぼ同じもので、違いは既定のアクセス制御だけ(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; }

変わったポイント

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 は既定が privatestruct は既定が 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 が自然データの塊
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. classstruct の違いとして正しいのは?

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 になり使い物にならない。③は正しく動くが冗長。④はそもそも型の名前が付かない。