C++編【言語解説】 第12章 カプセル化

先頭へ戻る

この章と同じ(または似た)情報を扱うページが、Modern C++編 (C++11/14/17 対応) の以下の章にあります。

この章の概要

この章の概要です。

アクセス指定子

クラスが持つ重要な機能の1つに、アクセス指定子があります。これは、前章の Studentクラスの例の中で既に登場していて、「public:」という記述がそれです。

class Student {
    std::string  mName;   // 名前
    int          mGrade;  // 学年
    int          mScore;  // 得点

public:
    void SetData(std::string name, int grade, int score);
    void Print();
};

publicキーワードは、クラスか構造体の定義の中で使用できます。「public:」という記述によって、それ以降に書かれた部分が「公開」されます

「公開」というのは、そのクラスのオブジェクトを使って、「student.Print();」とか「student->Print();」のようにして使えるということです。クラス定義の外部から使える(見える)ということで「公開」と表現されます。

先ほどの例では、「公開」されていないメンバ変数が3つありました。「公開」されていないので、「student.mGrade = 2;」とか「student->mScore = 100;」のような使い方ができません。このような状態を「非公開」であると言います。

クラスの場合、デフォルトで「非公開」であるとみなされますから、「公開」したいメンバは、「public:」よりも後ろに記述しなければなりません

なお、明示的に「非公開」であることを表したい場合は、privateキーワードを使います。

class Student {
private:  // 以下のメンバは「非公開」
    std::string  mName;   // 名前
    int          mGrade;  // 学年
    int          mScore;  // 得点

public:  // 以下のメンバは「公開」
    void SetData(std::string name, int grade, int score);
    void Print();
};

アクセス指定子には、public、private の他にもう1つ、protected があります。これは、第27章で解説します。

「公開」と「非公開」の効果を試しておきましょう。

class Test {
public:
    int mPublicValue;
    void SetValue(int value);

private:
    int mPrivateValue;
};

void Test::SetValue(int value)
{
    mPrivateValue = value;
}

int main()
{
    Test test;
    test.mPublicValue = 100;
    test.mPrivateValue = 200;  // コンパイルエラー
    test.SetValue(200);
}

「公開」されている mPublicValue へはアクセスできますが、「非公開」の mPrivateValue にはアクセスできず、コンパイルエラーとなります。一方、SetValueメンバ関数のようなメンバ関数の内部からは、「公開」「非公開」問わず、アクセスできます。

ちなみに、好みの問題でもありますが、「公開」のメンバを先頭側に集めて、「非公開」のメンバを後ろに集めることが多いです。これは、クラスを利用する人の立場で見れば、「非公開」のメンバには興味が無いからです(呼び出せないので)。当サイトでも、この考え方を取って、次のような順番で書くことにします。

class Student {
public:
    void SetData(std::string name, int grade, int score);
    void Print();

private:
    std::string  mName;   // 名前
    int          mGrade;  // 学年
    int          mScore;  // 得点
};

また、public や private が何度も登場することは問題ありません。そのため、極端ではありますが、次のように書くこともできます。

class Student {
public: void SetData(std::string name, int grade, int score);

private: std::string  mName;   // 名前

public: void Print();
	
private: int          mGrade;  // 学年
private: int          mScore;  // 得点
};

勿論、こんな分かり難い書き方は避けましょう。

メンバを「非公開」にする機能をうまく使うことは、OOP では非常に重要なことです。例えば、メンバ変数score が「公開」されていると、「student.score = 500;」のように書けてしまいます。もし、得点として正常な値が、0~100 であるとすれば、この代入は不正です。クラスの外部から、メンバ変数score を書き換える手段が、SetDataメンバ関数経由だけに限定されていれば、

void Student::SetData(std::string name, int grade, int score);
{
    assert(0 <= score && score <= 100);

    mName = name;
    mGrade = grade;
    mScore = score;
}

このようにチェックを入れることができます。「あらかじめチェックを入れておくことができる」とも言えますが、「後から追加できる」ということも重要です。「student.mScore = 100;」のような代入を至る所でされてしまった後では、このような追加は難しいです。

なお、メンバの一部を「非公開」にすることによって、データや処理内容、型といった各種要素を外部から見えないようにすることを、OOP の用語で、カプセル化と言います。カプセル化は、OOP の特徴の中でも、特筆すべき重要な考え方です。

setter と getter

何を「公開」して、何を「非公開」にすればいいかですが、基本方針としては、クラスのメンバ変数は絶対に「非公開」にすることです。とりあえず、この基本方針だけは守り通してみて下さい。

もし、「student.score = 100;」のように、メンバ変数への代入が必要なら、そのための「公開」のメンバ関数を用意します。このような、メンバ変数の値をセットするためのメンバ関数は、セッター(setter) と呼ばれます。

また、「score = student.score;」のように、メンバ変数の値を取得したい場合は、やはりそのための「公開」のメンバ関数を用意します。このような、メンバ変数の値を返すためのメンバ関数は、ゲッター(getter) と呼ばれます。

具体的には、次のようになります。

class Student {
public:
    void SetScore(int score);
    int GetScore();

private:
    int          mScore;  // 得点
};
void Student::SetScore(int score)
{
    assert(0 <= score && score <= 100);
    mScore = score;
}

int Student::GetScore()
{
    return mScore;
}

setter と getter は単なる呼び名に過ぎず、正体は普通のメンバ関数ですから、セットで作らないといけない訳ではありません。むしろ、必要性の無いものは作らないことが重要です。

なお、メンバ関数の名前としては、setter は「Set~」とすることが多く、getter は「Get~」とするか単に「Score」のような名前にすることが多いです。

実際には、getter の方は更に良くできます。そのためには、次のように、const修飾子を付加します。

class Student {
public:
    int GetScore() const;
};
int Student::GetScore() const
{
    return mScore;
}

メンバ関数の引数リストの後ろのところに「const」と書くと、constメンバ関数という特殊なメンバ関数になります。このような const を付けると、そのメンバ関数内では、メンバ変数を書き換えられなくなります。getter が、本当に getter としての役割に徹していれば、常に const を付けられるはずです(return するだけですから)。

もし、あるオブジェクトが const指定されていたり、constポインタがオブジェクトを指していたりする場合、そのオブジェクトの constメンバだけしか呼び出せません。

#include <iostream>
#include "student.h"

int main()
{
    Student s1;
    const Student s2;
    Student* p1 = &s1;
    const Student* p2 = &s1;

    s1.SetScore(100);
    s2.SetScore(100);  // エラー
    p1->SetScore(100);
    p2->SetScore(100); // エラー

    std::cout << s1.GetScore() << "\n"
              << s2.GetScore() << "\n"
              << p1->GetScore() << "\n"
              << p2->GetScore() << std::endl;
}

SetScoreメンバ関数は constメンバ関数でないので、const修飾子付きの s2 および、constポインタ経由でアクセスしている p2 からは呼び出すことができません。

constメンバ関数に限らず、const修飾子は、C++プログラミングにおいては非常に重要な機能であると言えます。const修飾子に関連する話題はかなり多くあるので、第18章で改めて取り上げます。

ところで、「student.mScore = 100;」で済むところを「student.SetScore(100);」と書いたり、「score = student.mScore;」で済むところを「score = student.GetScore();」と書いたりすることで、関数呼び出しのコストが増えて、効率が落ちるのが嫌だという人もいるかも知れません。

そもそも、この程度のことで目に見えてパフォーマンスが低下することはほぼ無いというのが答えですが、仮に問題となった場合には、インライン関数(第9章)を思い出して下さい。メンバ関数をインライン化することも可能です。

class Student {
public:
    inline void SetScore(int score);
    inline int GetScore() const;

private:
    int          mScore;  // 得点
};

// インライン関数の定義は、ヘッダファイル内に書く

void Student::SetScore(int score)
{
    assert(0 <= score && score <= 100);
    mScore = score;
}

int Student::GetScore() const
{
    return mScore;
}

インライン関数の定義は、ヘッダファイル側に書かなくてはいけないことに注意して下さい。なお、メンバ関数のインライン化は次のように書くこともできます。

class Student {
public:
    inline void SetScore(int score)
    {
        assert(0 <= score && score <= 100);
        mScore = score;
    }
    inline int GetScore() const
    {
        return mScore;
    }

private:
    int          mScore;  // 得点
};

実はこの場合、inlineキーワードすら不要です。

class Student {
public:
    void SetScore(int score)
    {
        assert(0 <= score && score <= 100);
        mScore = score;
    }
    int GetScore() const
    {
        return mScore;
    }

private:
    int          mScore;  // 得点
};

たまに面倒臭がって、インライン化するつもりもなく、メンバ関数の中身を上のように書いてしまう人がいますが、この書き方は、インライン化するという意志表示になるので注意して下さい。

クラスと構造体の違い

クラスと構造体は、"ほぼ"同一のものです。違いは、アクセス指定子のデフォルトが、クラスは「非公開」であるのに対して、構造体は「公開」であることだけです。従って、次の CStudentクラスと、SStudent構造体は、意味としては同一になります。

class CStudent {
public:
    void SetScore(int score);

private:
    int mScore;
};

struct SStudent {
    void SetScore(int score);  // デフォルトで public なので、これは「公開」

private:
    int mScore;
};

ただし、あくまでも別の型ですから、CStudent型の変数に SStudent型の値を代入するようなことはできません。

あえてクラスと構造体を使い分けるとすれば、C言語的な構造体が必要な場合には struct を使い、そうでなければ class を使うということです。つまり、「複数の変数の集合体」というだけなら、struct にしておくということです。

前に、クラスのメンバ変数は必ず「非公開」にすると書きましたが、C言語的な構造体の意味合いで struct を使うのなら、例外的にメンバ変数を「公開」にしても良いでしょう。


練習問題

問題① setter と getter を両方用意する代わりに、メンバ変数のメモリアドレスを返すメンバ関数を用意することは、良いアイディアでしょうか?

問題② 「非公開」なメンバ関数は、どのような用途に使えるでしょうか?

問題③ 次の「正方形」を表す構造体について、カプセル化の観点から、問題点を指摘して下さい。

// 正方形を表す構造体
struct Square {
    int			mSide;    // 1辺の長さ
    int			mArea;    // 面積
};


解答ページはこちら

参考リンク



更新履歴

'2014/4/24 新規作成。



前の章へ(第11章 クラス)

次の章へ(第13章 コンストラクタとデストラクタ)

C++編のトップページへ

Programming Place Plus のトップページへ


このエントリーをはてなブックマークに追加
rss1.0 取得ボタン RSS