# ScopedGeneric [ScopedGeneric](https://source.chromium.org/chromium/chromium/src/+/main:base/scoped_generic.h;l=84;drc=5e2e66845d808530cc0adc5c411060a7faea54b7)というテンプレートがbaseライブラリの中にある。 これは何? ## Overview ScopedGenericはstd::unique_ptrに近い挙動をするが、大きな違いとしてポインタではなくオブジェクトのコピーを持つクラス。 Scopeのあるフォーマットを使うことで、ポインタでない型についてもset/reset/moveなどをいちいち実装せずに使えるよというヘルパーテンプレート。 Deleterは適宜カスタマイズしてやる必要がある。 主な使い方は以下のような感じ。 ```cpp= typedef ScopedGeneric<int, internal::ScopedFDCloseTraits> ScopedFD; ``` ScopedGenericのテンプレートに使いたい型をいれたものをtypedefする。 このとき適切なdeleterを実装し、オブジェクトのclean upを行えるようにしておく。 ## 実装 テンプレートは以下。 ```cpp= template<typename T, typename Traits> class ScopedGeneric { ... ``` `T`が実際に格納する値の型で、`Traits`はdeleterなどいくつかのメソッドを持つクラス。 実際のデータは[`data_`](https://source.chromium.org/chromium/chromium/src/+/main:base/scoped_generic.h;l=288;drc=5e2e66845d808530cc0adc5c411060a7faea54b7)の中の[`generic`](https://source.chromium.org/chromium/chromium/src/+/main:base/scoped_generic.h;l=95;drc=5e2e66845d808530cc0adc5c411060a7faea54b7)に格納されている。これは`T`型。 [`get`](https://source.chromium.org/chromium/chromium/src/+/main:base/scoped_generic.h;l=239;drc=5e2e66845d808530cc0adc5c411060a7faea54b7)で`data_.generic`の値をconst参照することができる。 [`reset`](https://source.chromium.org/chromium/chromium/src/+/main:base/scoped_generic.h;l=138;drc=5e2e66845d808530cc0adc5c411060a7faea54b7)では`data_.generic`にresetするために値`value`を代入する。 なおdefault引数にtraits_type::InvalidValue()が定義されている。InvalidValueはポインタにおけるnullptrのような概念で、invalidな値を表すものをTraitsに決めさせているのでそれとの比較によって意味のある値が入っているかを確認することができる。 [`release`](https://source.chromium.org/chromium/chromium/src/+/main:base/scoped_generic.h;l=149;drc=5e2e66845d808530cc0adc5c411060a7faea54b7)ときは逆に`data_.generic`にtraits_type::InvalidValue()を代入している。 このようにして代入されたInvalidな値は、実際[`is_valid`](https://source.chromium.org/chromium/chromium/src/+/main:base/scoped_generic.h;l=243;drc=5e2e66845d808530cc0adc5c411060a7faea54b7)が呼ばれた際に比較されvalidity checkが走る。 また`operator==`や`operator!=`は直感通りに実装されており、`data_.generic`同士の値の比較をする。 ## Traits Traitsとして渡すクラスは何でも良いわけではない。 いくつかの必要な関数の実装がなければコンパイルできない。 必要なのは - InvalidValue - Free - Acuire - Release の4つ。 以下[base::ScopedFD](https://source.chromium.org/chromium/chromium/src/+/main:base/files/scoped_file.h;l=101;drc=e4622aaeccea84652488d1822c28c78b7115684f)の具体例を照らし合わせながら確認する。 ScopedFDは`int`でfile descriptorのIDを持ち、そのオブジェクト自体がFDとの接続を保持している。 InvalidValueはnullptrの代わり。ポインタの場合nullcheckがinvalidチェックになったが、ScopedGenericでは常に値を持つので自明なinvalidな値はなく、自分で設定してやる必要がある。 [base::ScopedFDのInvalidValue](https://source.chromium.org/chromium/chromium/src/+/main:base/files/scoped_file.h;l=36;drc=e4622aaeccea84652488d1822c28c78b7115684f)は`-1`を返す。 ```cpp= static int InvalidValue() { return -1; } ``` 次にFreeはいい感じに解放してくれるための実装。[base::ScopedFDのFree](https://source.chromium.org/chromium/chromium/src/+/main:base/files/scoped_file.cc;l=23;drc=e4622aaeccea84652488d1822c28c78b7115684f)の場合、解放時にはFDとの接続を閉じてやる必要がある。 ```cpp= void ScopedFDCloseTraits::Free(int fd) { int ret = IGNORE_EINTR(close(fd)); if (ret != 0 && errno != EBADF) ret = 0; PCHECK(0 == ret); } ``` なお、この時file descriptorを正しく閉じずにcrashすることが重要、というコメントが書いてあるが、今回はScopedGenricがお題なのでbase::ScopedFDの詳細はスルーしよう。TODO(elkurin): Read 最後にAcquireとReleaseはそのオブジェクトの所有権を取ったり渡したりするためのメソッド。定義する必要がないこともある。所有権を真面目にtrackingしない場合は不要。 [base::ScopedFDのAcquire](https://source.chromium.org/chromium/chromium/src/+/main:base/files/scoped_file_linux.cc;l=55;drc=8e78783dc1f7007bad46d657c9f332614e240fd8)は以下の関数が`owned=true`で呼ばれる。 ```cpp= void UpdateAndCheckFdOwnership(int fd, bool owned) { if (CanTrack(fd) && g_is_fd_owned[static_cast<size_t>(fd)].exchange(owned) == owned && g_is_ownership_enforced) { CrashOnFdOwnershipViolation(); } } ``` `g_is_fd_owned`はatomic_boolの配列で、FDのmax sizeの4096個分ownされているかどうかのboolを持っている。 ## ScopedGenericOwnershipTracking [ScopedGenericOwnershipTracking](https://source.chromium.org/chromium/chromium/src/+/main:base/scoped_generic.h;l=81;drc=8e78783dc1f7007bad46d657c9f332614e240fd8)は宣言したScopedGenericのオブジェクトの所有権をtrackするかどうか選択するために使われる空struct。 以下のようにownershipをtrackしたいときに`Traits`を継承させて作れば良い。 ```cpp= struct BASE_EXPORT ScopedFDCloseTraits : public ScopedGenericOwnershipTracking { ``` この継承がどう使われるかというと、[std::derived_from](https://cpprefjp.github.io/reference/concepts/derived_from.html)という片方のクラスがもう一方から派生しているかを確認できるコンセプトを使って分岐する際に使われる。 ```cpp= void TrackAcquire(const T& value) { if constexpr (std::derived_from<Traits, ScopedGenericOwnershipTracking>) { if (value != traits_type::InvalidValue()) { data_.Acquire(static_cast<const ScopedGeneric&>(*this), value); } } } ``` この分岐がコンセプトを用いて書かれているので、ScopedGenericOwnershipTrackingを継承していない場合はTraitsがAcquire/Releaseを宣言していなくてもコンパイルエラーにならない。