[Chromium Maps](/szPe4BDiSAqq2Lk1DSycHw)でいろいろなmapの比較を確認した。
その中で[base::small_map](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:base/containers/small_map.h)だけよくわからなかったので、読んでみる。
## コンセプト
[base::small_map](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:base/containers/small_map.h)は、要素数が少ない間はarrayでもって、上限を超えたら他のmap型にフォールバックするバランス型のクラス。
なんのmap型にフォールバックするかは宣言時に選択し、arrayで持つ上限の要素数も指定できる。
## クラス宣言
small_mapクラスのテンプレートは以下。
```cpp=
template <typename NormalMap,
size_t kArraySize = 4,
typename EqualKey = typename internal::select_equal_key<
NormalMap,
internal::has_key_equal<NormalMap>::value>::equal_key,
typename MapInit = internal::small_map_default_init<NormalMap>>
class small_map {
...;
};
```
1つ目のテンプレート引数`NormalMap`はフォールバック用のmapの型を指定している。
またmap型で指定された `iterator::value_type` を値の方としてarrayでも使用する。
それ以外はデフォルト値を持っている。
`kArraySize`はarrayの上限を指定しており、この値を書き換えている使用例もある。
```cpp=
// kMaxNumSpatialLayersPlusOne を指定している
using InputSurfaceMap = base::small_map<
std::map<gfx::Size, std::unique_ptr<ScopedVASurface>, SizeComparator>,
kMaxNumSpatialLayersPlusOne>;
```
## 実装
デフォルトで使用するarrayとフォールバック用のmapは以下のように宣言されている。
```cpp=
union {
value_type array_[kArraySize];
NormalMap map_;
};
```
`array_`はサイズ`kArraySize`の固定長配列。
`map_`はNormalMap型で宣言されたなんかのマップ。
ところで[`union`](https://en.cppreference.com/w/cpp/language/union)という型は共用体と呼ばれるもので、複数の方のどれかを格納したい際に用いる。
初期化時は最初のメンバを持ち、その後は別のデータメンバに値を代入するとそのメンバがactivateされ、もとのやつは寿命が尽きる。なので、共用体のサイズは、メンバの中でもっとも大きいやつのサイズになっている。宣言は`struct`と同じように書ける。細かい仕様はまた調べてみる。
ここではまず`array_`がサイズ`kArraySize`で初期化される。
要素数が`kArraySize`を超えると、例えば以下のように`map_`がアクティブになる。
```cpp=
data_type& operator[](const key_type& key) {
...;
if (size_ == kArraySize) {
ConvertToRealMap();
return map_[key];
}
}
```
`map_`への切り替えは[ConvertToRealMap()](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:base/containers/small_map.h;l=551;drc=255b4e7036f1326f2219bd547d3d6dcf76064870)が行う。
`array_`の中身を`map_`に移し替えないといけないのでいきなりdeactivateするのはダメ。
まず`array_`から`temp`にすべて要素を移し替え`map_`をactivateしたら`temp`の値を挿入していっている。
この実装の中では`temp`を意図的にunionとして宣言している。これは`kArraySize`分の要素のコンストラクタを呼ぶコストを避けて、ただコピーするだけにしたいから。
その後以下の処理を行う。
```cpp=
size_ = kUsingFullMapSentinel;
// `map_`を初期化しておく。
functor_(&map_);
```
unionのどちらがアクティブかのフラグは[`size_`](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:base/containers/small_map.h;l=539;drc=255b4e7036f1326f2219bd547d3d6dcf76064870)で管理している。
`array_`使用時は`size_`はkArraySize以下の値で、`map_`を使用し始めたら[`kUsingFullMapSentinel`](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:base/containers/small_map.h;l=19;drc=255b4e7036f1326f2219bd547d3d6dcf76064870)という`size_t`型の最大値を代入して、`map_`を使用するかのフラグは `size_ == kUsingFullMapSentinel` で管理する。
### Look up
keyをもらってdataを返す関数。
Arrayのときとmapのときで挙動が変わる。
mapのときは普通にmapのそれぞれの型のlook upを使用する。
arrayの際はイテレータを後ろから探している。つまり最近追加された要素を早く探索しようとしている。
```cpp=
data_type& operator[](const key_type& key) {
// mapのとき
if (UsingFullMap()) {
return map_[key];
}
// arrayのとき
for (size_t i = size_; i > 0; --i) {
const size_t index = i - 1;
if (compare(array_[index].first, key)) {
return array_[index].second;
}
}
}
```
### 挿入
key-valueのペアであるvalue_typeを挿入する関数。
Loop upと同様にmapのときはその型のinsertを参照するだけ。
arrayのときは、初期化時に必要な要素分静的な配列を作っているので、適切なイテレータにつっこむだけ。
すでに存在する場合は挿入は行わないで対応するイテレータをそのまま返す。
```cpp=
std::pair<iterator, bool> insert(const value_type& x) {
// mapのとき
if (UsingFullMap()) {
std::pair<typename NormalMap::iterator, bool> ret = map_.insert(x);
return std::make_pair(iterator(ret.first), ret.second);
}
// すでに登録済みな時
for (size_t i = 0; i < size_; ++i) {
if (compare(array_[i].first, x.first)) {
return std::make_pair(iterator(array_ + i), false);
}
}
// arrayのとき
new (&array_[size_]) value_type(x);
return std::make_pair(iterator(array_ + size_++), true);
}
```
## おまけ
### サンプルコード
いろんなmapの宝庫みたいなファイルを見つけた。
[wayland_buffer_manager_gpu.h](https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.h;l=306-325;drc=15246b4169c99b4eb14176e2d20ad33553fdc7b7):
```cpp=
std::map<gfx::AcceleratedWidget, WaylandSurfaceGpu*>
widget_to_surface_map_; // Guarded by |lock_|.
base::flat_map<gfx::BufferFormat, std::vector<uint64_t>>
supported_buffer_formats_with_modifiers_;
base::small_map<std::map<gfx::AcceleratedWidget,
scoped_refptr<base::SingleThreadTaskRunner>>>
commit_thread_runners_;
```
### numeric_limits
[`kUsingFullMapSentinel`](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:base/containers/small_map.h;l=19;drc=255b4e7036f1326f2219bd547d3d6dcf76064870)の初期化に `std::numeric_limits<size_t>::max()` という標準ライブラリを使っていた。
これは実装が提供する算術型の性質を提供するライブラリで、min, max, lowest,is_exactなどいろいろな特徴を返してくれる。