C++編【言語解説】 第11章 クラス

この章の概要

この章の概要です。

オブジェクト指向プログラミング

この章から、C++ のオブジェクト指向プログラミングに関わる機能の解説を始めていきます。 この章では、最も重要な概念であるクラスを解説します。

クラスという用語は C++ に固有の言葉ではなく、オブジェクト指向プログラミングの世界での共通語のようなものです。 ですからまずは、オブジェクト指向プログラミングについて簡単に知っておきましょう。 なお、オブジェクト指向プログラミングは、略して OOP (Object Oriented Programming) と表記されることがあります。 文章を短く抑えるため、今後 OOP と書きます。

OOP での主役はオブジェクトです。
この章のテーマであるクラスというのは、オブジェクトの型のことです。 ここで「型」というのは、int型とか double型とか、MyData型(構造体)とかいうときの「型」です。 C++ には、クラス(クラス型)というものが追加されているという訳です。

OOP では、「複数のオブジェクトを用意し」、「それぞれが自分の仕事をこなし」、「必要があれば他のオブジェクトへ仕事を依頼する」ことで、 プログラムを構築するという考え方を取ります。 ここから先では、この3つの文の意味を順番に追ってみます。

OOP (「複数のオブジェクトを用意し」の意味)

先ほどの OOP の説明で、「複数のオブジェクトを用意し」というのがありました。 これは、あなた自身が C++ のソースコードの中で、クラスを定義し、そのクラス型の変数を定義するということです。 具体的に書くと、

// 生徒クラス
class Student {
	std::string  name;   // 名前
	int          grade;  // 学年
	int          score;  // 得点
};

int main()
{
	Student student1;  // オブジェクトを用意する(=クラス型の変数を定義する)
	Student student2;  // オブジェクトを用意する(=クラス型の変数を定義する)
}

正直、ただこれだけのことです。 この場合、2つの変数を定義したので、2つのオブジェクトが用意されたということです。

クラスを定義するには、classキーワードを使います。 構文はほとんど構造体と同じに見えると思いますが、実際、ここで定義した Studentクラスの場合は同じと考えて構いません。 違いは、次章で取り上げます。
なお、オブジェクトを用意することを、(クラスを)インスタンス化すると言います。 また、オブジェクトのことをインスタンス(実体)と呼ぶこともあります。

このように、C++ のクラスという概念は、単なる型に過ぎません。 int型や char型、double型といった、最初から用意されている型を組み合わせて、独自に都合の良い型を作り出せます (C言語の構造体も同じですよね?)

OOP (「それぞれが自分の仕事をこなし」の意味)

次に、「それぞれが自分の仕事をこなし」という部分です。 例えば、Studentクラスをインスタンス化したオブジェクトは、自分の名前・学年・得点を管理しており、 逆に言えば、それ以外のことには一切関知しないということです。

少し例を挙げましょう。 構造体でもそうですが、自身のメンバの値が勝手に初期化される訳ではありません。 構造体の場合、

student.name = "Saitou Takashi";
student.grade = 2;
student.score = 80;

のように、各メンバに値を代入していました。 クラスの場合も同じことをできなくは無いですが、普通はしませんし、OOP の観点から言えば、してはいけません(次章で取り上げます)。
代わりに、クラス定義の中に関数を追加し、これを使うようにします。

// 生徒クラス
class Student {
	std::string  name;   // 名前
	int          grade;  // 学年
	int          score;  // 得点
	
public:
	void SetData(std::string name, int grade, int score);
};

void Student::SetData(std::string name, int grade, int score)
{
	this->name = name;
	this->grade = grade;
	this->score = score;
}

int main()
{
	Student student;
	student.SetData("Saitou Takashi", 2, 80);
}

今まで、関数のプロトタイプ宣言を書いていたときの感覚で、クラス定義の中に宣言を書きます。 このようにして作られる関数を、メンバ関数と呼びます。 また、クラス内に含まれる変数はメンバ変数と呼ばれます。

なお、「public:」という記述が追加されていますが、これについては次章で説明しますが、 これがあることで、「student.SetData()」のように、メンバ関数を呼び出すことができるようになります。 オブジェクトからメンバ関数を呼び出すには、このように .演算子でつなげるだけです。 student がポインタなら、「student->SetData()」です。

もし、Studentクラスからインスタンス化されたオブジェクトが2つあると(student1, student2 という名前とします)、 「student1.SetData()」「student2.SetData()」は、別のオブジェクトに対する操作になります。 また、逆に言うと、オブジェクトが無いのに SetData() を呼び出すことは不可能です。 オブジェクトが存在しない、C言語(非OOP)との決定的な違いがここにあります。

メンバ関数の定義の方ですが、「Student::SetData」のようにクラス名で修飾します。
関数内には、「this->name」という記述がありますが、 thisキーワードは、このメンバ関数を呼び出す元となったオブジェクト(を指すポインタ)のことを意味しています。 「student1.SetData()」のように呼び出したときは、this は &student1 のことになるし、 「student2.SetData()」なら、this は &student2 です。 もちろん、「student1->SetData()」なら、this は student1 のことです。
従って「this->name」というのは、メンバ変数の name のことを指しています。 this を使わずに、「name = name;」と書いてしまうと、仮引数のname へ仮引数の name を代入することになってしまうので、 区別を付けるために、this が必要です。

「name = name;」のような記述になってしまうのを防ぐには、this を使う以外に、 メンバ変数の名前に、「m」や「_」を付けるように徹底するという方法もよく使われています。 例えば、「mName」「m_name」「name_」といった感じです。 ちなみに「_name」のように、頭にアンダーバーの付いた名前は、処理系が定義する名前と被る可能性があるので、良い方法ではありません(C言語編第3章参照)。
当サイトでは今後、メンバ変数の頭に「m」を付ける方法を使います。

なお、クラス定義はヘッダファイル、メンバ関数の定義はソースファイル側に記述するのが一般的です。 ただし、メンバ関数がインライン関数(第9章)であったり、関数テンプレート第9章であったりする場合には、 定義もヘッダファイル側に記述します

OOP (「必要があれば他のオブジェクトへ仕事を依頼する」の意味)

最後に、「必要があれば他のオブジェクトへ仕事を依頼する」という部分です。 例えば、Studentクラスのメンバ変数の値を出力したいとき、いつものように std::cout に渡すことになりますが、 実は、std::cout もオブジェクトなのです。
実際にどう書けばいいかと言うと、次のようになります。

// student.h

#ifndef STUDENT_H
#define STUDENT_H

#include <string>

class Student {
	std::string  mName;   // 名前
	int          mGrade;  // 学年
	int          mScore;  // 得点
	
public:
	void SetData(std::string name, int grade, int score);
	void Print();
};

#endif
// student.cpp

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

void Student::SetData(std::string name, int grade, int score)
{
	mName = name;
	mGrade = grade;
	mScore = score;
}

void Student::Print()
{
	std::cout << mName << " "
	          << mGrade << " "
	          << mScore << std::endl;
}
// main.cpp

#include "student.h"

int main()
{
	Student student;
	student.SetData("Saitou Takashi", 2, 80);
	student.Print();
}

実行結果:

Saitou Takashi 2 80

より直接的に、std::cout << student; のように書けるようにする方法も存在しますが、 ここでは扱いません。第32章で説明します。

各オブジェクトは、自分の仕事をこなすことに集中するという考え方ですから、 自分の担当外のことは、他のオブジェクトへお願いするのが基本です。
クラスを作る際には、そのクラスの担当範囲というものをしっかりと意識して、 それを逸脱しないように気を付けて下さい

また一方で、クラスを使いだすと、全てをクラスにしなくてはいけないかのように思う人も多いようです。 C++ では、クラスを使うことを強制していませんから、必要に応じて、これまで通りの単独の関数があっても構いません。

クラス

ここまで説明してきたように、OOP ではクラスが重要な役割を持っています。 プログラミング言語の種類によって、クラスの意味合いには多少の違いがありますが、 C++ においては単なる型です。

C++プログラマは、int型、char型といった組み込み型や、std::string のような標準ライブラリで用意されている型を自由に組み合わせて、 独自のクラスを定義できます。
これだけならC言語の構造体でも同じことですが、構造体は変数の塊に過ぎないのに対して、クラスはメンバ関数を持つことができます。 このおかげで、必要となる機能もワンセットにして定義できるため、部品としての独立性を増すことができます。 また、余計な機能を付けないようにすることで、堅牢性を増すこともできます。
ちなみに、メンバ変数だけしか無いようなら構造体にするか、他のクラスへ移すのが適切であることが多いです。 また、メンバ関数だけしか無いようなら、通常の関数にしたり(必要に応じて、名前空間に含める)、他のクラスへ移すのが適切かも知れません。

何をクラスにすればいいのでしょうか?
非常にさまざまな考え方があります。 「オブジェクト=もの」なので、1つの「もの」を1つの「クラス」とするのが基本であるだとか、 名詞として表現されるものを「クラス」にするのが良いなどと言われることがあります。 しかし、個人的には、あまり気にしすぎないのが無難だと思います。
重要なのは「1つのクラスに機能を詰め込みすぎない」ことです。とりあえず、これだけでも守って下さい。 例えば、「生徒」「先生」「学校」といった要素を、1つのクラスだけで賄おうとすることは、大抵誤った設計です。

C++ のクラスには、他にも様々な機能がありますが、一度に理解することは困難なので、 次章以降で、少しずつ明らかにしていきます。


練習問題

問題@ この章で扱った Studentクラスを使い、2人の生徒のオブジェクトをインスタンス化して下さい。

問題A 2人の生徒の得点を比べて、点数が高い方の生徒の名前を出力する関数を作成して下さい。 必要に応じて、Studentクラスを修正して構いません。

問題B 以下の仕様で、「先生」を表すクラスを作って下さい。


解答ページはこちら

参考リンク

更新履歴

'2014/4/19 新規作成。



前の章へ

次の章へ

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

Programming Place Plus のトップページへ