導入
C++のstd::unordered_mapなどのハッシュベースのコンテナを使用する際、std::shared_ptrをキーとして扱いたい場面は少なくありません。例えば、オブジェクトの所有権を共有しつつ、特定のインスタンスに関連付けられたメタデータを管理する場合などです。しかし、標準状態ではstd::shared_ptrに対してハッシュ関数が提供されていないため、そのままではコンパイルエラーとなります。本記事では、この課題を解決し、安全かつ実用的にshared_ptrをキーとして扱う方法を解説します。
基礎知識
std::unordered_mapは、キーに対してハッシュ値を計算するstd::hashと、等価性を判定するstd::equal_toを使用して要素を管理します。std::shared_ptr自体はスマートポインタであり、内部で生のポインタを管理しています。std::shared_ptrをキーにするということは、「ポインタの指すアドレス値」に基づいてハッシュを計算し、比較を行うことを意味します。
実装/解決策
C++11以降、std::hashの特殊化を行うことで、任意の型をunordered_mapのキーとして利用可能になります。具体的には、std::hashを継承またはオーバーロードし、std::shared_ptrが保持するポインタのアドレス(get()メソッドで取得可能)をハッシュ化対象として渡す実装を行います。
サンプルプログラム
以下のコードは、std::shared_ptrをキーとして使用するためのハッシュ構造体定義と、実際にmapとして利用する例です。
include <iostream>
include <memory>
include <unordered_map>
include <string>
// std::hashをshared_ptr向けに特殊化する構造体
struct SharedPtrHasher {
template <typename T>
size_t operator()(const std::shared_ptr<T>& p) const {
// ポインタの生アドレスをハッシュ化の元とする
return std::hash<T>{}(p.get());
}
};
int main() {
// unordered_mapの第3テンプレート引数にハッシュ関数を指定
std::unordered_map<std::shared_ptr<std::string>, int, SharedPtrHasher> myMap;
auto ptr1 = std::make_shared<std::string>("Key1");
auto ptr2 = std::make_shared<std::string>("Key2");
// マップへの挿入
myMap[ptr1] = 100;
myMap[ptr2] = 200;
// 検索の確認
if (myMap.count(ptr1)) {
std::cout << "ptr1の値: " << myMap[ptr1] << std::endl;
}
return 0;
}
応用・注意点
現場での実装において、以下の点に注意してください。
1. ポインタの比較と値の比較
上記のハッシュ化は「オブジェクトのアドレス」に基づいています。もし「オブジェクトの中身(値)が同じなら同一のキーとみなしたい」場合は、ハッシュ関数と等価比較関数(std::equal_toの代替)の両方を実装し、中身の比較を行う必要があります。
2. 循環参照の回避
shared_ptrをキーにする際、もしそのmap自体がshared_ptrの指すオブジェクトのメンバである場合、循環参照が発生しメモリリークの原因となります。このような場合はstd::weak_ptrの使用を検討するか、mapの保持期間を厳密に管理してください。
3. C++20以降の検討
C++20以降では、標準ライブラリの改善が進んでいますが、それでもshared_ptrのハッシュ化は明示的な実装が必要です。プロジェクトのコーディング規約に従い、ハッシュ関数は再利用可能なヘッダファイルにまとめておくことを推奨します。

コメント