はじめに
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つの方法を解説しました。コンテナにソートのルールを教えることで、自作の型も基本データ型と同じように、連想コンテナで自在に扱えるようになります。
