はじめに
C++のstd::set
やstd::map
は、キーの大小関係に基づいて、内部のデータを自動的にソートします。int
やstring
のような基本型は、a < b
のように、コンパイラが比較方法を知っているため、そのままキーとして使えます。
しかし、自作したProduct
のような構造体をキーにしようとすると、「2つのProduct
オブジェクトのどちらが『小さい』のか分からない」とコンパイラに怒られてしまいます。
この問題を解決するには、主に以下の2つの方法があります。
- 比較演算子
<
をオーバーロードする: そのクラスの「自然な順序」を定義する。 - 独自の比較オブジェクトを提供する: ソートのルールを外部から指定する。
この記事では、これら2つの方法を、具体的なサンプルコードと共に解説します。
方法1: 比較演算子 <
をオーバーロードする
最もシンプルで一般的な方法です。キーとして使いたい構造体に対して、operator<
を定義します。std::set
やstd::map
は、キーの比較が必要になると、このoperator<
を自動的に見つけて利用します。
サンプルコード
#include <iostream>
#include <string>
#include <set>
using namespace std;
// 商品構造体
struct Product {
int id;
string name;
};
// 1. Product構造体のための operator< をグローバル関数として定義
// idメンバーを基準に比較する
bool operator<(const Product& lhs, const Product& rhs) {
return lhs.id < rhs.id;
}
int main() {
// 2. 通常通り set<Product> を宣言
set<Product> product_set;
product_set.insert({102, "みかん"});
product_set.insert({101, "りんご"});
product_set.insert({103, "ぶどう"});
// idの昇順で出力される
for (const auto& product : product_set) {
cout << "ID: " << product.id << ", 商品名: " << product.name << endl;
}
return 0;
}
解説: bool operator<(const Product& lhs, const Product& rhs)
関数を定義することで、Product
オブジェクト同士が <
演算子で比較された際の動作(この場合はid
で比較)をコンパイラに教えています。
方法2: 独自の比較オブジェクトを提供する
「ID順だけでなく、商品名順でもソートしたい」といったように、複数のソート順を使い分けたい場合や、元の構造体を変更したくない場合に有効な方法です。
比較ルールを定義した、関数のように振る舞うオブジェクト(関数オブジェクト)を作成し、set
やmap
のテンプレート引数として渡します。
サンプルコード
#include <iostream>
#include <string>
#include <map>
using namespace std;
// 商品構造体(今回は operator< を定義しない)
struct Product {
int id;
string name;
};
// 1. 商品名を基準に比較するための、独自の比較オブジェクトを定義
struct CompareProductByName {
bool operator()(const Product& lhs, const Product& rhs) const {
return lhs.name < rhs.name;
}
};
int main() {
// 2. mapの3番目のテンプレート引数に、比較オブジェクトを指定
map<Product, int, CompareProductByName> stock;
stock[{102, "みかん"}] = 30;
stock[{101, "りんご"}] = 50;
stock[{103, "ぶどう"}] = 20;
// 商品名のアルファベット順で出力される
for (const auto& [product, quantity] : stock) {
cout << "商品名: " << product.name << ", 在庫: " << quantity << endl;
}
return 0;
}
解説:
struct CompareProductByName { ... }
:()
演算子をオーバーロードした関数オブジェクトです。map<Product, int, CompareProductByName>
:map
の3番目のテンプレート引数に、キーの比較に使う型を指定します。ここに自作の比較オブジェクトを指定することで、デフォルトの<
の代わりに、そのオブジェクトが比較ロジックとして使われます。
使い分けのポイント
operator<
のオーバーロード:- そのオブジェクトにとって「自然で、最も基本的な順序」が一つだけ決まっている場合に適しています。(例: ID順)
- 独自の比較オブジェクト:
- 複数の順序でソートしたい場合や、ライブラリで定義されている構造体など、元の定義を変更できない場合に適しています。
まとめ
今回は、自作した型を std::set
や std::map
のキーとして使うための2つの方法を解説しました。コンテナにソートのルールを教えることで、自作の型も基本データ型と同じように、連想コンテナで自在に扱えるようになります。