
C++入門|複数のインスタンス
同じ設計図から生まれても、戦士ごとに力は違う。C++の複数インスタンスを使えば、同じクラスから個性あるオブジェクトを何体でも作れます。
ここまで、C++のオブジェクト指向として、クラス、オブジェクト、インスタンス、メンバ変数、メンバ関数について学んできました。
前回までの例では、Warriorクラスという戦士の設計図を作り、そこからwarriorというインスタンスを1つ生成して使いました。
ただ、クラスの本当の便利さは、1つのインスタンスだけを作るところでは終わりません。
C++では、1つのクラスから複数のインスタンスを生成できます。
ドラゴンボール風にたとえるなら、Warriorクラスは「戦士を作るための設計図」です。
その設計図から、亀仙流の戦士、神流の戦士、惑星戦士流の戦士など、複数の戦士を作ることができます。
同じWarriorクラスから作られていても、それぞれの戦士は別々の名前、別々の戦闘力、別々の気の増幅倍率を持てます。
つまり、同じ設計図から生まれたとしても、インスタンスが違えば中身もふるまいも変わります。
| 用語 | 意味 | ドラゴンボール風のイメージ |
|---|---|---|
| クラス | オブジェクトの設計図 | Warriorという戦士設計図 |
| インスタンス | クラスから作った実体 | 実際に修行場にいる戦士 |
| 複数のインスタンス | 同じクラスから複数の実体を作ること | warrior1、warrior2のように複数の戦士を作る |
| メンバ変数 | インスタンスごとに持つデータ | 名前、通常戦闘力、気の倍率 |
| メンバ関数 | インスタンスごとに実行できる動作 | 状態表示、増幅後の戦闘力計算 |
今回の記事では、1つのWarriorクラスから、warrior1とwarrior2という2人の戦士を作ります。
それぞれに違う名前、通常時の戦闘力、気の増幅倍率を設定し、同じメンバ関数を呼び出したときに、結果が変わることを確認します。
図:1つのクラスから複数の戦士インスタンスを作る

この図が示していること
この図では、1つのWarriorクラスから、warrior1とwarrior2という2つのインスタンスを作る流れを表しています。
同じWarriorクラスから作られていても、warrior1とwarrior2は別々のオブジェクトです。
そのため、name、basePower、boostRateの値をそれぞれ別々に持てます。
C++では、このように同じクラスから複数のインスタンスを作り、それぞれ違う状態で動かすことができます。
1つのインスタンスだけでは分かりにくいクラスのメリット
クラスを初めて学ぶとき、最初は「これなら普通の変数と関数だけでも書けるのでは」と感じることがあります。
たしかに、1人の戦士だけを扱う短いプログラムなら、変数と関数を直接書くだけでも動かせます。
しかし、戦士が2人、3人、さらにチーム単位に増えていくと、クラスの便利さがはっきり見えてきます。
たとえば、戦士を2人扱う場合、通常の変数だけで書こうとすると、次のような変数を用意することになります。
| 戦士 | 必要な変数の例 |
|---|---|
| 1人目 | name1、basePower1、boostRate1 |
| 2人目 | name2、basePower2、boostRate2 |
人数が増えるほど、変数名も増えて管理が大変になります。
一方、Warriorクラスを使えば、次のように書けます。
Warrior warrior1, warrior2;この1行で、Warriorクラスから2人分のインスタンスを生成できます。
それぞれのインスタンスは、自分専用のメンバ変数を持ちます。
| インスタンス | 持っているデータ |
|---|---|
| warrior1 | warrior1.name、warrior1.basePower、warrior1.boostRate |
| warrior2 | warrior2.name、warrior2.basePower、warrior2.boostRate |
このように、クラスを使うと、同じ構造を持つデータをきれいにまとめられます。
ドラゴンボール風に言えば、同じ戦士設計図から複数の戦士を作り、それぞれの戦士に違う修行データを持たせられるということです。
今回作成するプログラムの内容
今回のプログラムでは、Warriorクラスを使って2人の戦士を作ります。
1人目はwarrior1です。
2人目はwarrior2です。
それぞれに、名前、通常時の戦闘力、気の増幅倍率を設定します。
| インスタンス | 名前 | 通常時の戦闘力 | 気の増幅倍率 |
|---|---|---|---|
| warrior1 | Leo | 1500 | 3 |
| warrior2 | Noa | 2200 | 2 |
この2人に対して、同じshowStatusメンバ関数を呼び出します。
warrior1.showStatus();
warrior2.showStatus();同じshowStatusを呼び出しているのに、表示結果はそれぞれ異なります。
なぜなら、warrior1とwarrior2が持っているメンバ変数の値が違うからです。
サンプルプログラムの構成
今回は、Chap3_02プロジェクトとして、次の3つのファイルを作成します。
| ファイル | 役割 |
|---|---|
| warrior.h | Warriorクラスの宣言を書く |
| warrior.cpp | Warriorクラスのメンバ関数の定義を書く |
| main.cpp | 複数のインスタンスを生成して使う |
前回までと同じように、クラス宣言、メンバ関数の定義、利用する処理をファイルごとに分けます。
ドラゴンボール風にたとえるなら、次のような役割です。
| ファイル | ドラゴンボール風のイメージ |
|---|---|
| warrior.h | 戦士の設計図 |
| warrior.cpp | 戦士の技や表示処理の実装書 |
| main.cpp | 修行場で戦士たちを呼び出す司令室 |
Warriorクラスの宣言
まず、Warriorクラスの宣言を書きます。
プロジェクト/ファイル名: Chap3_02/warrior.h
#ifndef _WARRIOR_H_
#define _WARRIOR_H_
#include <string>
class Warrior {
public:
// 戦士の名前
std::string name;
// 通常時の戦闘力
double basePower;
// 気の増幅倍率
double boostRate;
// 気を高めた後の戦闘力を計算する
double calcBoostedPower();
// 戦士の状態を表示する
void showStatus();
};
#endif // _WARRIOR_H_warrior.hの解説
このファイルでは、Warriorクラスの形を宣言しています。
Warriorクラスには、3つのメンバ変数があります。
| 型 | メンバ変数 | 内容 |
|---|---|---|
| std::string | name | 戦士の名前 |
| double | basePower | 通常時の戦闘力 |
| double | boostRate | 気の増幅倍率 |
さらに、2つのメンバ関数があります。
| 戻り値の型 | メンバ関数 | 内容 |
|---|---|---|
| double | calcBoostedPower | 増幅後の戦闘力を計算して返す |
| void | showStatus | 戦士の状態を表示する |
このクラス宣言は、戦士を作るための設計図です。
この設計図から作られたインスタンスは、必ずname、basePower、boostRateを持ち、calcBoostedPowerとshowStatusを使えます。
ヘッダーガードの役割
warrior.hの先頭と末尾には、次の記述があります。
#ifndef _WARRIOR_H_
#define _WARRIOR_H_
// Warriorクラスの宣言
#endif // _WARRIOR_H_これは、ヘッダーファイルの2重インクルードを防ぐための仕組みです。
同じヘッダーファイルが複数回読み込まれたときに、同じクラスが何度も宣言されるとエラーになることがあります。
ヘッダーガードを書いておくことで、Warriorクラスの宣言が重複して読み込まれるのを防げます。
C++でクラスをヘッダーファイルに書くときは、この形を覚えておくと安心です。
Warriorクラスのメンバ関数を定義する
次に、Warriorクラスのメンバ関数の中身を書きます。
プロジェクト/ファイル名: Chap3_02/warrior.cpp
#include "warrior.h"
#include <iostream>
using namespace std;
// 気を高めた後の戦闘力を計算する
double Warrior::calcBoostedPower() {
return basePower * boostRate;
}
// 戦士の状態を表示する
void Warrior::showStatus() {
cout << "=== 戦士データ ===" << endl;
cout << "名前:" << name << endl;
cout << "通常時の戦闘力:" << basePower << endl;
cout << "気の増幅倍率:" << boostRate << "倍" << endl;
cout << "増幅後の戦闘力:" << calcBoostedPower() << endl;
}warrior.cppの解説
このファイルでは、Warriorクラスのメンバ関数を定義しています。
注目したいのは、次の書き方です。
double Warrior::calcBoostedPower()
void Warrior::showStatus()関数名の前にWarrior::を付けています。
これは、calcBoostedPowerとshowStatusがWarriorクラスに属するメンバ関数であることを示しています。
| 書き方 | 意味 |
|---|---|
| Warrior::calcBoostedPower | WarriorクラスのcalcBoostedPower関数 |
| Warrior::showStatus | WarriorクラスのshowStatus関数 |
| :: | クラスに属していることを示すスコープ解決演算子 |
ドラゴンボール風にたとえるなら、calcBoostedPowerやshowStatusという技が、Warrior設計図に登録された技であることを示しているようなものです。
calcBoostedPower関数の役割
calcBoostedPower関数では、増幅後の戦闘力を計算しています。
return basePower * boostRate;basePowerは通常時の戦闘力です。
boostRateは気を高めたときの倍率です。
この2つを掛けることで、増幅後の戦闘力を求めています。
| basePower | boostRate | 増幅後の戦闘力 |
|---|---|---|
| 1500 | 3 | 4500 |
| 2200 | 2 | 4400 |
ここで大切なのは、basePowerやboostRateをそのまま書いている点です。
クラスの外からアクセスするときは、warrior1.basePowerのようにインスタンス名が必要です。
しかし、Warriorクラスのメンバ関数の中では、自分自身のメンバ変数をそのまま名前だけで使えます。
showStatus関数の役割
showStatus関数では、戦士の情報を表示しています。
cout << "名前:" << name << endl;
cout << "通常時の戦闘力:" << basePower << endl;
cout << "気の増幅倍率:" << boostRate << "倍" << endl;
cout << "増幅後の戦闘力:" << calcBoostedPower() << endl;この中では、name、basePower、boostRateをそのまま使っています。
また、同じクラスのメンバ関数であるcalcBoostedPowerも、そのまま呼び出しています。
calcBoostedPower()つまり、showStatusは、Warriorインスタンスが持っているデータを使って、その戦士の状態を表示します。
warrior1から呼び出せば、warrior1のデータを表示します。
warrior2から呼び出せば、warrior2のデータを表示します。
図:同じshowStatusでもインスタンスごとに結果が変わる

この図が示していること
この図では、warrior1とwarrior2が同じshowStatusメンバ関数を持っていても、表示される結果が異なることを表しています。
warrior1はbasePowerが1500、boostRateが3なので、増幅後の戦闘力は4500です。
warrior2はbasePowerが2200、boostRateが2なので、増幅後の戦闘力は4400です。
同じクラスから作られたインスタンスでも、それぞれが別々のメンバ変数を持っているため、実行結果が変わります。
main.cppで複数のインスタンスを生成する
最後に、main.cppで2つのインスタンスを作って使います。
プロジェクト/ファイル名: Chap3_02/main.cpp
#include <iostream>
#include "warrior.h"
using namespace std;
int main(int argc, char** argv) {
// 2人の戦士インスタンスを生成する
Warrior warrior1, warrior2;
// warrior1の情報を設定する
warrior1.name = "Leo";
warrior1.basePower = 1500;
warrior1.boostRate = 3;
// warrior2の情報を設定する
warrior2.name = "Noa";
warrior2.basePower = 2200;
warrior2.boostRate = 2;
// warrior1の状態を表示する
cout << "warrior1のケース" << endl;
warrior1.showStatus();
// warrior2の状態を表示する
cout << "warrior2のケース" << endl;
warrior2.showStatus();
// プログラムが正常に終わったことを示す
return 0;
}実行結果は、次のようになります。
warrior1のケース
=== 戦士データ ===
名前:Leo
通常時の戦闘力:1500
気の増幅倍率:3倍
増幅後の戦闘力:4500
warrior2のケース
=== 戦士データ ===
名前:Noa
通常時の戦闘力:2200
気の増幅倍率:2倍
増幅後の戦闘力:4400複数のインスタンスを生成する書き方
このプログラムで最も大切なのは、次の行です。
Warrior warrior1, warrior2;これは、Warriorクラスからwarrior1とwarrior2という2つのインスタンスを生成しています。
| 書き方 | 意味 |
|---|---|
| Warrior | クラス名 |
| warrior1 | 1つ目のインスタンス |
| warrior2 | 2つ目のインスタンス |
| Warrior warrior1, warrior2; | 同じクラスから2つのインスタンスを作る |
この1行によって、同じ設計図から2人の戦士が誕生します。
ドラゴンボール風に言えば、Warriorという戦士設計図から、LeoとNoaという2人の修行戦士を呼び出したようなイメージです。
インスタンスごとにメンバ変数は別物
次に、warrior1とwarrior2に値を設定している部分を見てみましょう。
warrior1.name = "Leo";
warrior1.basePower = 1500;
warrior1.boostRate = 3;
warrior2.name = "Noa";
warrior2.basePower = 2200;
warrior2.boostRate = 2;同じname、basePower、boostRateというメンバ変数を使っていますが、warrior1のものとwarrior2のものは別々です。
| 書き方 | 対象 |
|---|---|
| warrior1.name | warrior1が持つname |
| warrior2.name | warrior2が持つname |
| warrior1.basePower | warrior1が持つbasePower |
| warrior2.basePower | warrior2が持つbasePower |
warrior1.basePowerに1500を代入しても、warrior2.basePowerには影響しません。
同じように、warrior2.boostRateに2を代入しても、warrior1.boostRateには影響しません。
これは、warrior1とwarrior2が別々のインスタンスだからです。
同じ名前のメンバでもインスタンスが違えば別物
クラスから作られたインスタンスは、それぞれ自分専用のメンバ変数を持ちます。
そのため、同じbasePowerという名前でも、warrior1のbasePowerとwarrior2のbasePowerは別物です。
| インスタンス | basePowerの値 | boostRateの値 | calcBoostedPowerの結果 |
|---|---|---|---|
| warrior1 | 1500 | 3 | 4500 |
| warrior2 | 2200 | 2 | 4400 |
この仕組みがあるから、同じWarriorクラスから複数の戦士を作り、それぞれ違う状態で扱えます。
ドラゴンボール風に言えば、同じ修行場で生まれた戦士でも、1人目は亀仙流で気の倍率が高く、2人目は神流で基礎戦闘力が高い、というように個性を持てるわけです。
メンバ関数もインスタンスごとに動きが変わる
次に、メンバ関数を呼び出している部分を見てみましょう。
warrior1.showStatus();
warrior2.showStatus();同じshowStatus関数を呼び出していますが、結果は違います。
なぜなら、showStatusの中で使われるname、basePower、boostRateが、呼び出したインスタンスのものになるからです。
warrior1.showStatus()では、warrior1のデータが使われます。
warrior2.showStatus()では、warrior2のデータが使われます。
| 呼び出し | 使われるデータ |
|---|---|
| warrior1.showStatus() | warrior1.name、warrior1.basePower、warrior1.boostRate |
| warrior2.showStatus() | warrior2.name、warrior2.basePower、warrior2.boostRate |
つまり、メンバ関数は同じ名前でも、どのインスタンスから呼び出すかによって、使うデータが変わります。
インスタンスの違いを図で整理する
warrior1とwarrior2は、同じWarriorクラスから作られています。
しかし、メモリ上では別々のオブジェクトとして存在します。
Warrior warrior1, warrior2;
+------------+ +------------+
| warrior1 | | warrior2 |
| name | | name |
| basePower | | basePower |
| boostRate | | boostRate |
| showStatus | | showStatus |
+------------+ +------------+同じ構造を持っていますが、中に入る値は別々です。
warrior1
name = Leo
basePower = 1500
boostRate = 3
warrior2
name = Noa
basePower = 2200
boostRate = 2このように、クラスは同じでも、インスタンスごとに状態を持てるのがオブジェクト指向の大きな特徴です。

図:warrior1とwarrior2は別々の状態を持つ

この図が示していること
この図では、warrior1とwarrior2が別々の状態を持っていることを表しています。
同じWarriorクラスから作られていても、warrior1のbasePowerとwarrior2のbasePowerは別々です。
そのため、warrior1.basePowerを変更しても、warrior2.basePowerには影響しません。
同じメンバ関数calcBoostedPowerを使っても、計算に使われる値が違うため、結果も変わります。
このプログラムの処理の流れ
このプログラムの処理を順番に見てみましょう。
| 手順 | 処理 | 内容 |
|---|---|---|
| 1 | Warrior warrior1, warrior2; | 2つのインスタンスを生成する |
| 2 | warrior1に値を設定 | Leo、1500、3を設定する |
| 3 | warrior2に値を設定 | Noa、2200、2を設定する |
| 4 | warrior1.showStatus(); | warrior1の情報を表示する |
| 5 | warrior2.showStatus(); | warrior2の情報を表示する |
この流れを見ると、warrior1とwarrior2が別々に扱われていることが分かります。
特に大切なのは、同じshowStatusを呼び出していても、それぞれのインスタンスが持つデータを使って表示している点です。
1つのクラスから複数のインスタンスを作れる意味
クラスは設計図です。
設計図は1つでも、そこから複数の実体を作れます。
Warriorクラスも同じです。
Warriorクラスは1つだけですが、そこからwarrior1、warrior2、さらにwarrior3、warrior4と、複数のインスタンスを作れます。
Warrior warrior1, warrior2, warrior3;このように書けば、3人の戦士を作れます。
それぞれの戦士は、同じメンバ変数とメンバ関数を持っています。
しかし、値はそれぞれ別々に設定できます。
| インスタンス | 役割のイメージ |
|---|---|
| warrior1 | 亀仙流の戦士 |
| warrior2 | 神流の戦士 |
| warrior3 | 惑星戦士流の戦士 |
同じWarriorクラスから作られたとしても、名前や戦闘力を変えれば、まったく違う戦士として扱えます。
複数インスタンスが便利な場面
複数のインスタンスは、同じ種類のデータをたくさん扱いたいときに便利です。
たとえば、戦士を複数人扱うプログラムを考えると分かりやすいです。
| やりたいこと | 複数インスタンスが役立つ理由 |
|---|---|
| 2人の戦士を比較する | warrior1とwarrior2に別々の戦闘力を持たせられる |
| チームを作る | 同じWarriorクラスから複数の戦士を作れる |
| 修行結果を表示する | 各戦士ごとにshowStatusを呼び出せる |
| 戦闘力ランキングを作る | 各インスタンスのcalcBoostedPowerの結果を比較できる |
クラスを使うと、同じ構造のデータを自然に増やせます。
ドラゴンボール風に言えば、1人の戦士だけでなく、チーム全体をプログラムで扱えるようになるということです。
複数のインスタンスで押さえたいポイント
複数のインスタンスを使うときは、次のポイントを押さえておきましょう。
| ポイント | 内容 |
|---|---|
| 1つのクラスから複数のインスタンスを作れる | Warrior warrior1, warrior2; のように書ける |
| インスタンスごとにメンバ変数は別々 | warrior1.basePowerとwarrior2.basePowerは別物 |
| 同じメンバ関数でも結果は変わる | 各インスタンスのデータを使って処理する |
| ドット演算子で対象を指定する | warrior1.showStatus()、warrior2.showStatus() |
| クラスは設計図、インスタンスは実体 | 同じ設計図から複数の戦士を作れる |
今回のポイントは、同じクラスから作られたインスタンスでも、それぞれが独立したデータを持つということです。
warrior1はwarrior1のname、basePower、boostRateを持っています。
warrior2はwarrior2のname、basePower、boostRateを持っています。
そのため、warrior1の値を変更しても、warrior2の値は変わりません。
C++のオブジェクト指向では、この仕組みを使うことで、同じ種類のものを複数扱いやすくなります。
戦士クラスを1つ作れば、そこから何人もの戦士を生み出せます。
そして、それぞれの戦士が自分だけの状態を持ち、自分のメンバ関数で動けるようになります。
これが、複数のインスタンスを使う大きなメリットです。
