traitsの利用する場面(メモ)

最近仕事について色々考えています。今の環境は正直自分のためになっていないので、何とか変える努力というかそういうのが必要だと切実に感じています。

とまぁそういうことは置いておいて、今回はC++の話題です。
boostとかC++0xとか、最新鋭の強力なライブラリを使いこなしている方にとっては、traitsというテクニックは最早当然のものだと思います。
このtraitsというテクニック、特にSTLで非常に多く、そして深く利用されています。

とは言っても、中々理解しずらいテクニックでもあります(少なくとも私にとって)。
なので、事例を示しつつ、なんとか理解できるようになりたいと思います。

まずは以下のようなクラスがあったとします。継承を利用しています。

class Hoge {
pubilc:
    virtual ~Hoge() {}
    virtual char func() = 0;
};

class HogeB : public Hoge {
public:
    virtual char func() {return 'B';}
};

class HogeC : public Hoge {
public:
    virtual char func() {return 'C';}
};

うーん、何の約にも立たないですね。まぁそこはとりあえず無視して・・・。
この例では、抽象クラスHogeを継承して、HogeBとHogeCを定義しています。Hogeには、純粋抽象関数funcが定義されていて、charを返す関数を、HogeBとHogeCでそれぞれ定義しています。

さて、ここで、Hoge::funcをcharだけではなく、intとかdoubleとかを返せるようにしたくなりました。でも別のクラスを定義したくなんてないので、次のように書き換えました。

template<class T>
class Hoge {
pubilc:
    T func()  // さて、どんな値を返せばいいんだ?
};

// イメージでは以下のように返したい。
template<>
class Hoge<int> {
public:
    int func() {return 0;}
};

template<>
class hoge<char> {
public:
   char func() {return 'A';}
};

とりあえずテンプレートにしました。でも、Hogeの特化テンプレートは作成できますが、特化されていない他の型に対するHogeを作成することはできません。
その前に、こんな場合はどんな値を返せっちゅーんじゃ、という感じになってますね。

それに下のように、クラステンプレートの特化はなんかいやです。特にこの場合、テンプレートパラメータが大量に利用されていたら、その分書き換える量が甚大になって、テンプレートの恩恵が受けられなくなります。
ここで、traitsが登場します。ここではシンプルにするため、traits自体は次のような定義であるとします。

template<class T>
struct hoge_traits {};

んー?という感じですが、これを利用してさっきのHogeテンプレートを書き換えてみます。

template<class T>
class Hoge {
pubilc:
    typedef  hoge_traits<T>::traits_type traits_type
    traits_type func() {return hoge_traits<T>::f();}
};

template<>
struct hoge_traits<char> {
    typedef  char  traits_type;
    static char f() {return 'A'};
};

template<>
struct hoge_traits<int> {
    typedef  int  traits_type;
    static int f() {return 0};
};

これで、他の型に対しても何とか動作するようになります。hoge_traitsの特化テンプレートを用意していないクラスについては、このクラスを利用することができなくなっていますが、これは誤った利用法を制御するのに役立ちます。多分。
hoge_traits::traits_typeについては、hoge_traitsの元の定義内でやっていても特に問題にはなりません。

書いていて、あまり役立ちそうにないというのがなんだかあれですが・・・。前の例と後の例を見比べてもらえればわかるかもしれませんが、前の例では明らかに無理がある記述がありましたが、traitsを利用した方は、Hogeテンプレートを(それなりに)すっきりと記述できているのがわかるかと思います。typedefはちょっと見づらいんですがそれがtraitsクオリティ。

traitsはオブジェクトの特性を表すため、「継承先クラスの特定の関数の内部だけ、各クラス別々にしたい」というような場合、traitsが力を発揮します。継承元クラスを継承したテンプレートを作成し、特定関数の内部をtraitsを利用して書き換え、後は各クラス毎のtraitsを記述する、という方法を利用すると、クラスの定義が氾濫することなく収めることができます。

ただし、traitsを利用すると、あるオブジェクトの定義(特性も定義の一部だとすると)が分散してしまうことが懸念されます。そんなことがないように、同じ名前空間で管理するとか、_traitsのように名前を工夫するとかすると、単純な継承では難しいことが意外とあっさりできたりするかもしれません。


久々にまともなことを書いている気がします。RubyとかPytyonとか色々触っていますが、やっぱり自分の第一言語C/C++だなぁ、という気がします。でもRubyとかPythonとかSchemeとか触りたいですよね。もっと頑張っていこう自分。

ここ違うくね?とかいう突っ込みは大歓迎です。多分どっか間違っていると思います(ぇ