技術文章>C++・STL・テンプレートプログラミング
C++ で型に依存しないコーディング(ジェネリック プログラミング とほぼ同意)と言えば、 以下のようなテンプレートを使ったコーディングである。
1: template<typename T> 2: T max(T a, T b) 3: { 4: return a > b ? a : b; 5: }
上記のように関数やクラスやクラスメソッドを定義しておけば、型が変わってもソースコードを修正する必要がない。 それらを使う側で、関数やクラスをインスタンス化するときに、具体的な型を指定するだけだ。 つまり、型に依存しないコーディングは設計の変更容易性(Ease of Changing)を向上させるわけだ。
本稿では、上記同様に変更容易性を向上させるコーディングテクニックを紹介する。
C++ の基本的なコーディングテクニックなので、知ってる人は知ってると思うが、
筆者は、時々このテクニックを忘れて(というか面倒くさがって?)コーディングしてしまい後で後悔するので、
自戒の意味を込めて本稿を書いておく。
以下は簡単なクラスの宣言例である。
1: class Hoge 2: { 3: public: 4: Hoge(char *ptr) : m_ptr(ptr); 5: public: 6: char *ptr() { return m_ptr; } 7: void setPtr(char *ptr) { m_ptr = ptr; } 8: private: 9: char *m_ptr; 10: }
上記クラスは型 char を使っている。
クラスのメンバ変数や、関数・関数引数などは当然ながら型を指定するわけだが、
その型が変更されると、ソースコードの多くの箇所での変更作業が発生する。
クラスの宣言・実装部分はもちろん、以下のようにクラスを利用している部分の変更作業も必要だ。
1: char *buf = "..."; 2: Hoge aHoge(buf); 3: char *ptr = aHoge.ptr();
型の変更作業は、単純に型名を置換するわけにはいかない。
同じ型が別のクラスなどで使われている場合があるからだ。
したがって、この作業は単純作業ではあるが、ミスを犯しやすく、神経を使う面倒な作業となる。
Hoge クラスがプロジェクトの非常に多くの箇所で使われている場合は、多大な時間を要する作業になってしまう。
つまり、このようなコーディングは型変更に対する変更容易性がよろしくないというわけだ。
クラス内で使用する型が変更される可能性がある場合は、以下のように typedef で型名を宣言し、 型名を使用する部分はすべて宣言した型名にしておくとよい。
1: class Hoge 2: { 3: public: 4: typedef char char_type; 5: public: 6: Hoge(char_type *ptr) : m_ptr(ptr); 7: public: 8: char_type *ptr() { return m_ptr; } 9: void setPtr(char_type *ptr) { m_ptr = ptr; } 10: private: 11: char_type *m_ptr; 12: }
Hoge クラスを利用する側は以下のようにコーディングしておく。
1: Hoge::char_type *buf = "..."; 2: Hoge aHoge(buf); 3: Hoge::char_type *ptr = aHoge.ptr();
このようにコーディングしておけば、Hoge で使用する型を char から short に変更する場合は、 下記の様に Hoge クラスの型名宣言行だけを変更するとよい。他の箇所の変更はいっさい必要ない。
1: class Hoge 2: { 3: public: 4: typedef short char_type; 5: ..... 6: }
厳密に言えば、2つ前のコードの1行目は変更が必要である。
char → short への変更であれば「L"..."」にしなくてはいけないし、
char → uchar への変更であれば「(uchar *)"..."」にしなくてはいけない。
が、このテクニックを使うことで変更必要箇所が激減することは事実である。
つまり、本コーディング法は型変更に対する変更容易性を著しく向上させるとうことである。
クラス宣言のコードを見て「このような場合はテンプレートを使うべき」という突っ込みを何人かから受けた。
まったくその通りかもしれないが、本稿のテクニックはテンプレートを使うかどうかとは独立な話である。
(※ テンプレートとは独立な話というのを強調するために、あえてテンプレートを使わない例をあげたのだが、
それが某軍団員の何かに触れてしまったのかもしれない)
本コーディング法は、先の使用例のようにテンプレートを使わない場合にも適用できるし、下記のようにテンプレートを使う場合にも適用できる。
1: template<typename T> 2: class Hoge 3: { 4: public: 5: typedef T char_type; 6: public: 7: Hoge(char_type *ptr) : m_ptr(ptr); 8: public: 9: char_type *ptr() { return m_ptr; } 10: void setPtr(char_type *ptr) { m_ptr = ptr; } 11: private: 12: char_type *m_ptr; 13: }
※ クラス内の型名の場合、テンプレート型名を使用するのと、宣言した型名を使用するのと、どちらがいいかは微妙な問題である。
テンプレートを使って、本コーディング法を使わない場合、以下の様に、型名が何箇所にも出現し、変更容易性が悪くなる。
1: Hoge<char> aHoge("..."); 2: char *ptr = aHoge.ptr();
下記のように、クラス内部で宣言された型名を使っておく方がよい。
1: typedef Hoge<char> HogeChar; 2: HogeChar aHoge("..."); 3: HogeChar::char_type *ptr = aHoge.ptr();
クラスで使用する型名を内部で宣言・公開し、その型名を使用することで、型変更に対する変更容易性を著しく向上させるコーディング法を紹介した。
おいらが、このコーディング法をちゃんと使うことで、後で型変更を行う時に後悔することが無くなるよう願っている。