Wednesday, February 01, 2006

Programming::C++::Overload - 演算子オーバーロードの嵐

templateをよく使うようになって、同時に演算子オーバーロードもよく使うようになりました。というのも、templateでジェネリックなアルゴリズムを書いていると、演算子オーバーロードしざるをえなくなってきます。単純に、Std.Plusを使うにしても、テンプレート引数に指定する型に対しoperator+ が定義されていなければ、Std.Plusをつかえません。

struct X
{
 X(int _x, int _y) : x(_x), y(_y) {}
 int x;
 int y;
};

void func()
{
 X a(0, 1), b(2, 3);
 int z = std::plus<X>(a, b); // これは不可能!
 ...
}

ではStd.Plusを使わなければよいのでしょうか?使わなくて済むなら使わなくて良いでしょう。しかし、使わなければならない状況もなきにしもあらずです。

Std.Plusではありませんが、最近プログラムを組んでいて直面した事態があります。Std.Mapを使いたい状況になり、キーは組み込み型(Plain Old Type)ではなく、複数のデータメンバを持つクラスです。例えば次のようなクラスです。

class X
{
public:
 X(const std::list<std::string>& x, const std::string& y) : x_(x), y_(y) {}
...
private:
 std::list<std::string> x_;
 const std::string y_;
};

ここでStd.Mapのキーとなる型に要求される事は、Std.Less、Std.LowerBoundがつかえる事。要はoperator==()とoperator<()を定義すればいいことになります。

operator==()は簡単です。全てのデータメンバとを比較するメソッドを書いてしまえば良いです。

class X
{
public:
 X(const std::list<std::string>& x, const std::string& y) : x_(x), y_(y) {}
 const std::list<std::string>& GetList() const { return x_; }
 const std::string& GetString() const { return y_; }
 bool operator==(const X& obj)
 {
  return (x_ == obj.String()) && (y_ == obj.GetList());
 }
...
private:
 std::list<std::string> x_;
 const std::string y_;
};


問題はoperator<()です。"小なり"を意味するわけですが、何を持ってして大小比較するのか?ということです。とりあえず上記の場合、文字列と文字列のリストがメンバとしてありますので、文字列x_と文字列のリストy_を全て連結した文字列とで大小比較する、ということを考えます。しかし、いちいち連結しているようでは非効率ですので、以下のようにします。

// class X内で定義する
bool operator<(const X& obj)
{
 if (x_ < obj.GetString()) {
  return true;
 }
 else {
  return (x_ == obj.GetString && y_ < obj.GetList()) ? true : false;
 }
}

Std.Listにはoperator<()が定義されていますので、上記のような比較が可能です。ここで、もしデータメンバy_が文字列リストstd::list<std::string&g;ではなく、テンプレート引数で与えられる、なにがしかのコンテナだったらどうでしょう。果たしてそのコンテナの型にはoperator<()は定義されているのでしょうか。

もし定義されていないとしても、begin()、end()メソッドが定義されており、イテレータが取り出せるならば、operator<()を次のように書き直せばよいことになります。

// class X内で定義する
bool operator<(const X& obj)
{
 if (x_ < obj.GetString()) {
  return true;
 }
 else {
  return (
   x_ == obj.GetString() &&
   std::lexicographical_compare(
    y_.begin(), y_.end(),
    obj.GetList()).begin(), obj.GetList.end()
   ) ? true : false;

 }
}

Std.Lessは比較する対象となる型にoperator<()が定義されていなければつかえません。Std.LexicographicalComapreはあるシーケンスのイテレータを与えれば大小比較できます(詳細は
http://www005.upp.so-net.ne.jp/episteme/html/stlprog/algorithm.html#lexicographical_compareで)。

こうなってくると、operator<()だけでなく、operator==()内も、文字列のリスト同士を"=="で比較していましたが、Std.Equalを使って書き直したくなります。

 // class X内で定義する
 const std::string& GetString() const { return y_; }
 bool operator==(const X& obj)
 {
  return (
   (x_ == obj.String()) &&
   (y_.size() == obj.GetList().size()) &&
   (std::equal(y_.begin(), y_.end(), obj.GetList().begin())
  );
 }


このように、Std.Mapなどジェネリックなライブラリと複雑なクラスを混在させて使うと、どうしても演算子オーバーロードが必要となってきます。演算子オーバーロードは使いすぎるとソースの可読性が下がるとか下がらないとかという話をよく見かけますが、演算子オーバーロードを使うと様々な部品が上手い具合に組み合わさり、動作してくれますね。C++の強力さに驚嘆させられます。

No comments: