C++ の勉強をしていて、クラス・インスタンスの配列とクラス・インスタンスのポインタの配列の区別がついていなかったので、そのメモ。
vehicle.h
#ifndef ___Class_Vehicle #define ___Class_Vehicle #include <string> class Vehicle { protected: std::string brand; std::string color; public: Vehicle(); Vehicle(std::string brd, std::string clr); virtual void print(); }; #endif
vehicle.cpp
#include <iostream> #include <fstream> #include <cstdlib> #include <iomanip> #include "vehicle.h" using namespace std; Vehicle::Vehicle() { brand = ""; color = ""; } Vehicle::Vehicle(string brd, string clr) { brand = brd; color = clr; } void Vehicle::print() { cout << "Brand: " << brand << endl; cout << "Color: " << color << endl; }
Car.h
#ifndef Car_H #define Car_H #include <string> #include "vehicle.h" class Car : public Vehicle { private: int seats; public: Car(); Car(std::string brd, std::string clr, int st); void print(); }; #endif
car.cpp
#include <iostream> #include <string> #include "Car.h" #include "vehicle.h" using namespace std; Car::Car() { seats = 0; } Car::Car(string brd, string clr, int st) : Vehicle(brd, clr) { seats = st; } void Car::print() { Vehicle::print(); cout << "Number of seats: " << seats << endl; }
Truck.h
#ifndef Truck_H #define Truck_H #include <string> #include "vehicle.h" class Truck : public Vehicle { private: std::string luggage; public: Truck(); Truck(std::string brd, std::string clr, std::string lug); void print(); }; #endif
truck.cpp
#include <iostream> #include <string> #include "Truck.h" #include "vehicle.h" using namespace std; Truck::Truck() { luggage = ""; } Truck::Truck(string brd, string clr, string lug) : Vehicle(brd, clr) { luggage = lug; } void Truck::print() { Vehicle::print(); cout << "Luggage: " << luggage << endl; }
やりたいこと
Vehicle型の配列を作成して、Vehicle型の派生オブジェクトのCar型およびTruck型のオブジェクトを配列に格納し、仮想関数print()によって格納したCarおよびTruckの情報を表示したい。Carには固有メンバとしてseats、Truckには固有メンバとしてluggageが定義されています。
以下が最初に書いたmain.cpp
main.cpp
#include <iostream> #include <string> #include "vehicle.h" #include "Truck.h" #include "Car.h" using namespace std; int main() { int vhc_num; string brd; string clr; int st; string lug; int vhc_type; cout << "How many vehicles do you have?: "; cin >> vhc_num; Vehicle* vhc_array = new Vehicle[vhc_num]; for (int i = 0; i < vhc_num; i++) { cout << "Which brand?: "; cin >> brd; cout << "What color?: "; cin >> clr; cout << "Is it car or truck? (Type 1 for car, type 2 for truck): "; cin >> vhc_type; if (vhc_type == 1) { cout << "How many seats?: "; cin >> st; Car car(brd, clr, st); vhc_array[i] = car; } else if (vhc_type == 2) { cout << "What luggage do you have?: "; cin >> lug; Truck trk(brd, clr, lug); vhc_array[i] = trk; } } for (int i = 0; i < vhc_num; i++) { vhc_array[i].print(); cout << "\n"; } return 0; }
このmain.cppおよびその他をコンパイルして実行すると。。。
Carの固有メンバseatsおよびTruckの固有メンバluggageの情報が表示されていません。
調べてみると配列の宣言の部分が良くなかった模様。
Vehicle* vhc_array = new Vehicle[vhc_num];
C++の特徴として基本クラスのポインタに派生クラスのオブジェクトのポインタを代入できるというものがあります。しかし、上記の書き方だとVehicle型のオブジェクトを格納するための配列の動的メモリ領域の確保となり、配列の要素としてオブジェクトそのものを格納することになります。(クラス・インスタンスの配列)
new Vehicle[vhc_num]
: Vehicle型の配列として、動的メモリ領域を確保
そのため、以下のように書き直す必要があります。
Vehicle** vhc_array = new Vehicle* [vhc_num];
new Vehicle* [vhc_num]
: Vehicle型のポインタ配列として動的メモリ領域を確保する。ポインタ型なので*をつける (クラス・インスタンスのポインタの配列)
また、この配列の宣言の変更に伴い、配列へのオブジェクトの格納の部分と仮想関数print()の呼び出し部分も書き換えます。
CarおよびTruckオブジェクトをそのまま格納していた部分を。。。
Car car(brd, clr, st);
vhc_array[i] = car;
Truck trk(brd, clr, lug);
vhc_array[i] = trk;
CarおよびTruckを引数とともに初期化し、そのアドレスをVehicle型のポインタ配列に格納します。(※ new演算子の戻り値は動的に確保したメモリのアドレス)
vhc_array[i] = new Car(brd, clr, st);
vhc_array[i] = new Truck(brd, clr, lug);
仮想関数print()もポインタからのアクセスになるため、vhc_array[i].print();
の部分をアロー演算子を用いたvhc_array[i]->print();
に書き換えます。
以下が修正したmain.cppになります。
#include <iostream> #include <string> #include "vehicle.h" #include "Truck.h" #include "Car.h" using namespace std; int main() { int vhc_num; string brd; string clr; int st; string lug; int vhc_type; cout << "How many vehicles do you have?: "; cin >> vhc_num; //Vehicle* vhc_array = new Vehicle[vhc_num]; Vehicle** vhc_array = new Vehicle* [vhc_num]; for (int i = 0; i < vhc_num; i++) { cout << "Which brand?: "; cin >> brd; cout << "What color?: "; cin >> clr; cout << "Is it car or truck? (Type 1 for car, type 2 for truck): "; cin >> vhc_type; if (vhc_type == 1) { cout << "How many seats?: "; cin >> st; //Car car(brd, clr, st); //vhc_array = &car; vhc_array[i] = new Car(brd, clr, st); } else if (vhc_type == 2) { cout << "What luggage do you have?: "; cin >> lug; //Truck trk(brd, clr, lug); //vhc_array = &trk; vhc_array[i] = new Truck(brd, clr, lug); } } for (int i = 0; i < vhc_num; i++) { //vhc_array[i].print(); vhc_array[i]->print(); cout << "\n"; delete vhc_array[i]; } delete [ ] vhc_array; return 0; }
コンパイルして実行すると。。。
今度はちゃんとseatsおよびluggageの情報が表示されました。
しかし、ふと疑問が。
vhc_array[i] = car;
vhc_array[i] = trk;
修正前のコードの上記の部分で、なぜ何もエラーが出なかったのか?Vehicle型の配列にCar型およびTruck型というVehicle型とは異なるオブジェクトを格納しているのだから、何かしらエラーが出そうなものですが、実際にはコンパイル自体は何事もなく通りました。
調べてみると、上記は実際にはCarやTruckオブジェクトを代入しているのではなく、どうやら基本クラスであるVehicleのコピーコンストラクタ(または代入演算子)を呼び出して、オブジェクトを配列に格納しているらしいということが分かりました。なのでVehicleとして初期化されたbrandおよびcolorメンバは配列にコピーされますが、Carの固有メンバseatsおよびTruckの固有メンバluggageの情報は抜け落ちてしまうようなのです。
ふーむ。
以上。
参考URL
http://program.station.ez-net.jp/special/handbook/cpp/class/derived-class.asp
http://d.hatena.ne.jp/mclh46/20100921/1285062316