株式会社エス・スリー・フォー

11.5 比較の関数オブジェクト

連想コンテナベースとソート済みシーケンスベースの各コレクションクラスは順序を内部的に保持します。この順序は比較オブジェクト、つまり、テンプレートを具体化するときに指定しなければならない比較クラスのインスタンスを基準にしています。比較オブジェクトは、const
メンバの
operator()、つまり関数呼び出し演算子を含んでいなければなりません。これは、引数としてコレクションクラスの
2 個の候補項目をとり、論理 (ブーリアン) 値を返します。コレクションクラス内で最初の引数の順序が 2
番目の引数を先行する場合は、戻り値が true になります。そうでない場合は、false
になります。このとき、ヘッダファイル <functional> にある標準 C++
ライブラリによって提供されている関数オブジェクトクラスのどれかを使用するのが最も簡単なことがあります。特に、項目を昇順に保持するには
less<T> を、項目を降順に保持するには
greater<T> を使用します。例:

  #include <functional>
  #include <rw/tvset.h>
  #include <rw/rwdate.h>

  RWTValSet<int, less<int> > mySet1;
  RWTValSet<RWDate, greater<RWDate> > mySet2;

ここでは、mySet1 は昇順に保持されている整数です。また、mySet2
は降順、つまり最新のものから新しい順に保持されているデータです。less<T>
の場合の式 (x < y) あるいは greater<T>
の場合の式 (x > y) が、型 T
のオブジェクトにおいて全体の順序付けを導き出す有効な式であるかぎり、標準 C++
ライブラリのこれらの比較オブジェクトを使用することができます。

11.5.1 全体の順序付けについて

前節で説明したように、比較オブジェクトはコレクションクラスの項目の型で全体の順序付けを導き出さなければなりません。つまり、比較オブジェクトの関数呼び出し演算子は次の
2 つの条件を満たさなければなりません。comp が比較オブジェクト、x、y、z
はコレクションクラスの候補項目 (ただし必ずしも別個とはかぎらない) であると仮定します。[1]

  I.      次のステートメントのどれかが真である。
  a) comp(x,y) が真で comp(y,x) が偽である。
  b) comp(x,y) が偽で comp(y,x) が真である。
  c) comp(x,y) が偽で comp(y,x) が偽である。
  (つまり、comp(x,y) と comp(y,x) の両方が
  真ではない)
  II.     comp(x,y) と comp(y,z) が真であるならば、comp(x,z) も真である (推移)。

I.a は、コレクションで x が y に先行しなければならないことを意味します。一方、I.b
は、y が x に先行しなければならないことを意味します。さらに、I.c は
このステートメントが真の場合、x と y
は同等であることを意味します。つまり、コレクションクラス内でどちらが先にきても構いません。これが、比較オブジェクトをパラメータとして持つテンプレートに対して行われる等値性の表記法です。例えば、連想コンテナベーステンプレートのメンバ関数
contains(T item) がコレクションクラスに item
と等値である項目が含まれているかどうかを調べるとき、comp(x,item) と comp(item,x)
は両方共 false であるなど、この関数はコレクションクラスで実際に項目 x を探します。ここで、==
演算子が使用されていないことに注目してください。直感的に理解するのが難しいため、最初は等値性に対して不信感を持つかもしれませんが、慣れるまでは誰でもこのように感じるので心配はいりません。コレクションクラス内ですべてを正しく位置付けるこの柔軟な方法には、すぐに慣れることができます。

比較オブジェクトは実装において一般に単純なものです。例を見てみましょう。

  class IncreasingLength {
  public:
  bool operator()(const RWCString& x, const RWCString& y)
  { return x.length() < y.length(); }
  };

  RWTValSet<RWCString,IncreasingLength> mySet;

ここでは、mySet
は、文字列の長さによって短いものから長いものに順序付けられた、文字列のコレクションを保持します。比較オブジェクトのインスタンスが全体の順序付けの必要条件を満たすことが確認できます。次の例では、mySet2
は降順で整数のコレクションを保持します。

  class DecreasingInt {
  public:
  bool operator()(int x, int y)
  { return x > y; }
  };

  RWTValSet<int, DecreasingInt> mySet2;

最初に見たときは比較の意味が逆のように思えるかもしれませんが、x が y
より大きい場合、比較オブジェクトはコレクションクラスで x が y
を先行すべきであることを指示しています。つまり、降順になります。最後に、不正な比較オブジェクトをみてみましょう。

  // DON'T DO THIS:
  class BadCompare {
  public:
  bool operator()(int x, int y)
  { return x <= y; }    // OH-OH! 全体の順序付け関係ではない
  };

  RWSetVal<int, BadCompare> mySet3;  // 不正な比較オブジェクト

なぜこれが間違いであるかを知るために、BadCompare のインスタンス badcomp
を検討します。x と y の両方に値 7 を使用したとき、I.a、I.b、I.c の 3
つのステートメントすべてが true
でなくなります。つまり、これは全体の順序付け関係の最初の規則に違反しています。[2]

 

注釈
  1. ^ Knuth (1973) から応用
  2. ^ 残念なことに、全体の順序の必要条件は論理的なものですが、意味論的なものではありません。そのため、コンパイラは有効でない比較子を拒否することができません。一般にこのようなコードはコンパイルされますが、予期しない動作を招きます。