ロベールのC++入門講座でC++を初歩から入門する(2日目)
2日目はクラスの勉強。この辺りからじっくり読んで行く。
6章 クラスの基礎
クラスは構造体に少し似ているらしい、ってのは何となく分かる。Cで実装するときとかちょっと似せて作るし。とにかく作ってみる。
/** * @file Class1.cpp */ #include <iostream> using namespace std; const int MAX_NAME = 16; class Student { public: char name[MAX_NAME+1]; int scoreJapanese; int scoreMath; int scoreEnglish; void Show(); }; void Student::Show() { cout << "Name : " << name << endl << "Japanese : " << scoreJapanese << endl << "Math : " << scoreMath << endl << "English : " << scoreEnglish << endl << endl; } int main() { Student students[] = { {"Ichiro", 73, 98, 86,}, {"Daisuke", 64, 45, 40,}, {"Hideki", 76, 78, 69,}, }; for (int key = 0; key < (sizeof(students) / sizeof(*students)); key++) { students[key].Show(); } return 0; }
C++のインスタンスってコンストラクタ関数を使わずとも、構造体の初期化みたいな形でも作れるのね。
派生クラス(サブクラス)から基底クラス(スーパークラス)への暗黙のキャストのことを、アップキャストと言うらしい。これはObjective-Cでも意識したので馴れてるはず。
コピーコンストラクタについて。Objective-Cはreference counter方式なので、確かコピーコンストラクタは無かったはず。Javaにはあったかな。これも試してみる。
/** * @file CopyConstructor.cpp */ #include <iostream> #include <cstring> using namespace std; #define IMPL_COPY_CONSTRUCTOR class Foo { private: int* m_number; public: Foo(); // constructor ~Foo(); // destructor #ifdef IMPL_COPY_CONSTRUCTOR Foo(const Foo& other); // copy constructor #endif void Set(int number); int Get(); }; Foo::Foo() { m_number = new int; *m_number = -1; } Foo::~Foo() { delete m_number; } #ifdef IMPL_COPY_CONSTRUCTOR Foo::Foo(const Foo& other) { m_number = new int; memcpy(m_number, other.m_number, sizeof(int)); } #endif void Foo::Set(int number) { *m_number = number; } int Foo::Get() { return *m_number; } int Trace(int number) { cout << "[Trace] #" << number << endl; } int ShowFoo(Foo foo) { Trace(2); cout << foo.Get() << endl; Trace(3); } int main() { Foo foo; foo.Set(10); Trace(1); ShowFoo(foo); Trace(4); return 0; }
IMPL_COPY_CONSTRUCTORが無効の場合、ShowFoo()を出たところでm_numberのメモリが解放されてしまうため、実行すると、
% ./CopyConstructor [Trace] #1 [Trace] #2 10 [Trace] #3 [Trace] #4 CopyConstructor(1030) malloc: *** error for object 0x10b1008d0: pointer being freed was not allocated *** set a breakpoint in malloc_error_break to debug
main()を出るところでm_numberの二重解放が走ってしまう。コピーコンストラクタが実装されていれば、ShowFoo()でfooを渡すタイミングでコピーされたfooに対してm_numberをallocateするため問題ない。つまりコンストラクタでメモリ確保を行うような実装をしている場合は、必ずコピーコンストラクタを実装する必要がある。
演算子オーバーロード。他の言語でも算術クラスを作ったことはないし、ちゃんと使ったことはないなー。あとでVector2DクラスとかFractionクラス自作してみようかなあ。operator=によるオブジェクトの自己代入は確かに実装しとくと便利かも。
7章 クラスの本領
主にクラスの継承について。
virtualとpure virtual。pure virtual functionは宣言時に0をつける。pure virtual functionをもつクラスは抽象クラス(abstract class)となるので、その型のオブジェクトは直接は作れないそうだ。試してみる。
/** * @file Pure.cpp */ #include <iostream> using namespace std; class Bar { protected: int m_number; public: virtual void Set(int number) = 0; // pure virtual function int Get() { return m_number; } }; class Baz: public Bar { public: void Set(int number) { m_number = number; }; }; int main() { Baz baz; baz.Set(1); cout << baz.Get() << endl; Bar bar; // ここでコンパイルエラー return 0; }
コンパイル結果は言われた通りになる。
make Pure g++ Pure.cpp -o Pure Pure.cpp: In function 'int main()': Pure.cpp:30: error: cannot declare variable 'bar' to be of abstract type 'Bar' Pure.cpp:7: note: because the following virtual functions are pure within 'Bar': Pure.cpp:12: note: virtual void Bar::Set(int) make: *** [Pure] Error 1
POD(plan old data)型とnon-POD型について。違いは前者は独自のコントラクタを持たない、後者は持つ。ただし、POD型は変数を宣言した時に、何の初期化もしなければデフォルトコンストラクタすら呼ばれない。確かにintやdouble型の変数は明示的に初期化しないといけないし。
仮想関数の仕組みには、仮想関数テーブル(virtual function table)というものがある。このテーブルを使って実際にどの関数を辿ればよいかを判別するらしい。
動的に確保したオブジェクトは基底クラスのポインタに入れておけば、異なる派生クラスでも柔軟に扱える。この辺りはDuck Typingっぽいことができそう。
デストラクタは仮想関数にすべし! そのクラスが継承されるケースを考えると、派生クラスのデストラクタが呼ばれるためにも仮想関数にしておいた方が安全。
次は「8章 ファイルとストリーム」から。