Slide 1

Slide 1 text

C++17 の新機能 アロケータ編 2018/10/4 鳥頭かりやマン 1

Slide 2

Slide 2 text

アロケータおいしい!!! 2 注:個人の感想です

Slide 3

Slide 3 text

多相アロケータ 3

Slide 4

Slide 4 text

多相アロケータ 4 N1850 Towards a Better Allocator Model http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1850.pdf N3525 Polymorphic Allocators http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3525.pdf N3726 Polymorphic Memory Resources http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3726.pdf N3816 Polymorphic Memory Resources - r1 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3816.pdf N3916 Polymorphic Memory Resources - r2 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3916.pdf 2005 年からってお前導入に10年以上かかってんのかよ…

Slide 5

Slide 5 text

多相アロケータ 5 導入の背景

Slide 6

Slide 6 text

多相アロケータ 導入の背景 6 コンテナの型にアロケータの 型が現れるのは控えめに 言って頭おかしい ※ Allocators@C++11 by Cryolite 先生 参照

Slide 7

Slide 7 text

多相アロケータ 導入の背景 7 // このコンテナの要素を… std::vector v1 = { 1, 2, 3 }; // このコンテナにコピーしたい… std::vector> v2 = v1;

Slide 8

Slide 8 text

多相アロケータ 導入の背景 8 // このコンテナの要素を… std::vector v1 = { 1, 2, 3 }; // このコンテナにコピーしたい…けど出来ない… std::vector> v2 = v1;

Slide 9

Slide 9 text

多相アロケータ 導入の背景 9 // このコンテナの要素を… std::vector v1 = { 1, 2, 3 }; // コピーするのめんどくさっ! std::vector> v2{v1.begin(), v1.end()};

Slide 10

Slide 10 text

多相アロケータ 導入の背景 10 // このコンテナのデータを… std::vector> v; // vector の引数を取る関数に… int calc(const std::vector&); // 渡したい… auto dt = calc(v);

Slide 11

Slide 11 text

多相アロケータ 導入の背景 11 // このコンテナのデータを… std::vector> v; // vector の引数を取る関数に… int calc(const std::vector&); // 渡したい…けど出来ない… auto dt = calc(v);

Slide 12

Slide 12 text

多相アロケータ 導入の背景 12 // このコンテナのデータを… std::vector> v; // vector の引数を取る関数に… int calc(const std::vector&); // コピーしてから渡すのマジかよ… std::vector v2{v.begin(), v.end()}; auto dt = calc(v2);

Slide 13

Slide 13 text

多相アロケータ 導入の背景 13 // このコンテナのデータを… std::vector> v; // 関数が弄れるならテンプレートにすれば… template typename A> int calc(const std::vector>&); // 渡せるけど、わざわざテンプレート書くのうぜぇ… auto dt = calc(v);

Slide 14

Slide 14 text

多相アロケータ 導入の背景 14 // この文字列と… std::string s; // この文字列を… std::basic_string, my_allocator> t; // 比較したい… auto result = s == t;

Slide 15

Slide 15 text

多相アロケータ 導入の背景 15 // この文字列と… std::string s; // この文字列を… std::basic_string, my_allocator> t; // 比較したい…けどできない… auto result = s == t;

Slide 16

Slide 16 text

多相アロケータ 導入の背景 16 // この文字列と… std::string s; // この文字列を… std::basic_string, my_allocator> t; // 比較したい…まぁ許容範囲かな… auto result = s == std::string_view(t);

Slide 17

Slide 17 text

多相アロケータ 導入の背景 17 // この文字列と… std::string s; // この文字列を… てかクラス名なげぇよ… std::basic_string, my_allocator> t; // 比較したい…まぁ許容範囲かな… auto result = s == std::string_view(t);

Slide 18

Slide 18 text

多相アロケータ 導入の背景 18 // この文字列と… std::string s; // この文字列を… てかクラス名なげぇよ… std::basic_string, my_allocator> t; // 比較したい…てか比較にアロケータ関係無いの にこれ出来ないのどう考えても規格のバグやろ… auto result = s == t;

Slide 19

Slide 19 text

多相アロケータ 19 対応

Slide 20

Slide 20 text

多相アロケータ 対応 20 標準アロケータ (std::allocator)魔改造 してメモリ割当戦略の 違いが型に出ないよう にしよう!!!

Slide 21

Slide 21 text

多相アロケータ 対応 21 標準アロケータ (std::allocator)魔改造 してメモリ割当戦略の 違いが型に出ないよう にしよう!!!

Slide 22

Slide 22 text

多相アロケータ 対応 22 チッ メモリ割当戦略の違い が型に出ない新しいア ロケータ作ろう!!!

Slide 23

Slide 23 text

多相アロケータ 対応 23 どうやって?

Slide 24

Slide 24 text

多相アロケータ 対応 24 多相アロケータ template std::pmr::polymorphic_allocator pmr ⇒ Polymorphic Memory Resource polymorphic ⇒ 多相 allocator ⇒アロケータ

Slide 25

Slide 25 text

多相アロケータ 対応 25 多相?

Slide 26

Slide 26 text

多相アロケータ 対応 26 polymorphic_allocator 自身ではメモリ割当、 解放処理を実装せず、 std::pmr::memory_resource (の具象派生クラス)にメモリ 割当、解放処理を委譲する。

Slide 27

Slide 27 text

多相アロケータ 対応 27 古き良き Strategy Pattern !!! つまり動的多相 GoF万歳!!!1!

Slide 28

Slide 28 text

多相アロケータ 対応 28 あ、あと、標準アロケータを多相にする 夢が破れて、標準コンテナを多相アロ ケータで使うのが面倒になっちゃったん で、標準コンテナに多相アロケータを使 うエイリアステンプレートを追加しよう!

Slide 29

Slide 29 text

多相アロケータ 対応 29 こんなやつ std::pmr::vector std::pmr::map … 全部あるよ (もちろん std::array 以外)

Slide 30

Slide 30 text

多相アロケータ 対応 30 定義はこんな感じ namespace std::pmr { template > using map = std::map>> } 要は、アロケータの型だけ固定化している。 テンプレートのデフォルトを変更しただけじゃないので注意。 (std::pmr::vector> とはできない) アロケータ用の テンプレート 引数は無い

Slide 31

Slide 31 text

多相アロケータ 31 polymorphic_allocator や memory_resource の詳細に行く 前に、アロケータのおさらい

Slide 32

Slide 32 text

多相アロケータ アロケータおさらい 32 そもそもアロケータとは?

Slide 33

Slide 33 text

多相アロケータ アロケータおさらい 33 そもそもアロケータとは? ⇓ メモリ割当をカスタマイズ するためのもの

Slide 34

Slide 34 text

多相アロケータ アロケータおさらい 34 そもそもアロケータとは? ⇓ メモリ割当をカスタマイズ するためのもの だけじゃない!!! ※ Allocators@C++11 by Cryolite 先生 参照

Slide 35

Slide 35 text

多相アロケータ アロケータおさらい 35 アロケータが無い場合の 動的メモリ使用方法 ⇓ new 式 delete 式

Slide 36

Slide 36 text

多相アロケータ アロケータおさらい 36 new式 ⇓ // メモリを割り当てて… void* addr = ::operator new(sizeof(T)); // ホントは T::operator new もあるかもだけど… // コンストラクタを呼び出す T* p = ::new(addr) T(args...);

Slide 37

Slide 37 text

多相アロケータ アロケータおさらい 37 A 型のアロケータ a ⇓ // メモリを割り当てて… A::pointer p = a.allocate(1); // ホントは std::allocator_traits::pointer std::allocator_traits::allocate(a, 1) // コンストラクタを呼び出す a.construct(std::addressof(*p), args...); // ホントは std::allocator_traits::construct(a, std::addressof(*p), args...)

Slide 38

Slide 38 text

多相アロケータ アロケータおさらい 38 delete式 ⇓ // デストラクタを呼び出して… p->~T(); // メモリを解放する ::operator delete(addr); // ホントは T::operator delete もあるかもだけど…

Slide 39

Slide 39 text

多相アロケータ アロケータおさらい 39 A 型のアロケータ a ⇓ // デストラクタを呼び出して… a.destroy(std::addressof(*p)); // ホントは std::allocator_traits::destroy(a, std::addressof(*p)) // メモリを解放する a.deallocate(p, 1); // ホントは std::allocator_traits::deallocate(a, p, 1)

Slide 40

Slide 40 text

多相アロケータ アロケータおさらい 40 アロケータのポイント① メモリ割当・解放とコンストラクタ・デスト ラクタ呼び出しが完全に分離している コンテナの要素型とアロケーションした いオブジェクトの型が必ずしも一致しな いので(むしろ一致する方が稀)

Slide 41

Slide 41 text

多相アロケータ アロケータおさらい 41 std::vectorの場合 T T T T T T T T T T T型配列が割り当てられる。 こんなコンテナはむしろ稀。 std::vector ポインタ サイズ 容量 アロケータ

Slide 42

Slide 42 text

多相アロケータ アロケータおさらい 42 std::forward_listの場合 ポインタ T ノード型(node的な)のメモリが割り当てられる。 ほとんどのコンテナがこれ。 forward_listにしたのはポインタ書くのが面倒だったから… std::forward_list ポインタ サイズ アロケータ ポインタ T ポインタ T

Slide 43

Slide 43 text

多相アロケータ アロケータおさらい 43 でも、指定されたアロケータは T型配列しかアロケーション できない…

Slide 44

Slide 44 text

多相アロケータ アロケータおさらい 44 でも、指定されたアロケータは T型配列しかアロケーション できない… ⇓ rebind

Slide 45

Slide 45 text

多相アロケータ アロケータおさらい 45 アロケータの rebind ⇓ 要素型が T 型のアロケータ型 A から、 要素型が U 型のアロケータ型 B を生成 (std::allocator ⇒ std::allocator みたいな感じ) using B = typename A::rebind::other; ホントは using B = std::allocator_traits::rebind_alloc

Slide 46

Slide 46 text

多相アロケータ アロケータおさらい 46 rebind を使えば、渡されたアロケータから 任意の型用のアロケータを生成可能 // 型T 用のアロケータから型U用のアロケータを生成 B b(a); ただし、常に以下が成り立つ必要がある assert(a == A(b));

Slide 47

Slide 47 text

多相アロケータ アロケータおさらい 47 アロケータの operator== は、一方で割り当てた メモリがもう一方で解放できる場合にのみ true を返さなきゃならない + a == A(b) じゃなきゃならない ⇓ a で割り当てられたメモリは A(b) でも解放できなきゃならない

Slide 48

Slide 48 text

多相アロケータ アロケータおさらい 48 C++11 から、アロケータは オブジェクト毎に状態を 持てるようになった。 ※ Allocators@C++11 by Cryolite 先生 参照 しかし、rebind したアロケータは 状態を共有しなければならない 状態を共有しないとメモリ解放無理でしょ…

Slide 49

Slide 49 text

多相アロケータ アロケータおさらい 49 つまりこんな感じ これ割と重要な要件です… でもこれに触れてる日本語の記事を見かけない… T 型用 アロケータ a U 型用 アロケータ b a と b と A(b)に 共有の状態 rebind rebind T 型用 アロケータ A(b)

Slide 50

Slide 50 text

多相アロケータ アロケータおさらい 50 ちなみに、実際は rebind だけじゃ なくて、普通のコピーやムーブでも 状態を共有しなくちゃならない。 アロケータって引数としてコピーされたりするから当たり前っちゃあ当たり前なんだけど… しかも、ムーブされた元も状態を 保持し続けなきゃならない。 アロケータにはムーブなんてなかったんや…

Slide 51

Slide 51 text

多相アロケータ アロケータおさらい 51 多相アロケータは型に メモリ割当戦略が現れない

Slide 52

Slide 52 text

多相アロケータ アロケータおさらい 52 多相アロケータは型に メモリ割当戦略が現れない ⇓ 多相アロケータはオブジェクト毎に メモリ割当戦略が異なる

Slide 53

Slide 53 text

多相アロケータ アロケータおさらい 53 多相アロケータは型に メモリ割当戦略が現れない ⇓ 多相アロケータはオブジェクト毎に メモリ割当戦略が異なる ⇓ 多相アロケータはオブジェクト毎に 状態を持つ

Slide 54

Slide 54 text

多相アロケータ アロケータおさらい 54 多相アロケータは strategy pattern になっているので、 strategy オブジェクト(memory_resource)側に メモリ割当に関する状態が持たれている。 多相アロケータ本体は、memory_resource へのポインタを持っているだけ。 さすがにちゃんと考えられている… てかそのために strategy pattern にしたのかな、教えてエロい人…

Slide 55

Slide 55 text

多相アロケータ アロケータおさらい 55 アロケータのポイント② メモリ割当・解放だけじゃなくて、 コンストラクタ、デストラクタ呼び出しも カスタマイズ可能 a.construct(std::addressof(*p), args...); a.destroy(std::addressof(*p));

Slide 56

Slide 56 text

多相アロケータ アロケータおさらい 56 素朴な疑問 コンストラクタ、デストラクタ呼び出しを カスタマイズできるのは分かったけど、 そもそも、コンストラクタ、デストラクタ 呼び出しなんてカスタマイズしたい?

Slide 57

Slide 57 text

多相アロケータ アロケータおさらい 57 答え アロケータをコンテナの要素に 伝搬したい時に、コンストラクタ 呼び出しのカスタマイズが必要 それ以外のケースやデストラクタ呼び出しのカスタマイズって必要な時ありますかね? 教えてエロい人!

Slide 58

Slide 58 text

多相アロケータ アロケータおさらい 58 答え アロケータをコンテナの要素に 伝搬したい時に、コンストラクタ 呼び出しのカスタマイズが必要 それ以外のケースやデストラクタ呼び出しのカスタマイズって必要な時ありますかね? 教えてエロい人! 勉強会後追記 デバッグログとかはありますね…

Slide 59

Slide 59 text

多相アロケータ アロケータおさらい 59 string の vector の場合のイメージ(普通バージョン) 絵心が無さ過ぎてcpprefjp から拝借… stringは、コンテナの アロケータオブジェク トとは異なる、自分独 自のアロケータオブ ジェクトを持っている。

Slide 60

Slide 60 text

多相アロケータ アロケータおさらい 60 string の vector の場合のイメージ(伝搬バージョン) 絵心が無さ過ぎてcpprefjp から拝借… stringはアロケータオ ブジェクトを持ってい るものの、コンテナの アロケータオブジェク トと状態を共有してい る。

Slide 61

Slide 61 text

多相アロケータ アロケータおさらい 61 素朴な疑問その2 そんなのコンテナに要素格納する時に、 コンテナのアロケータオブジェクトを 取得して、コンストラクタに明示的に 指定してやれば済む話しじゃないの?

Slide 62

Slide 62 text

多相アロケータ アロケータおさらい 62 答えその2 それはそうなんだけど、 毎回必ず手動でやるのは面倒だし 間違いも起きやすい。

Slide 63

Slide 63 text

多相アロケータ アロケータおさらい 63 手動で指定する例 // カスタムアロケータ MyAlloc のオブジェクト a MyAlloc a; // カスタムアロケータ MyAlloc を使った文字列型 MyStr using MyStr = std::basic_string, MyAlloc>; // MyStr を格納する、アロケータオブジェクト a を使ったvector std::vector> v(a); // アロケータオブジェクト a を使った要素格納 v.emplace_back("first element", a); v.emplace_back("second element", a);

Slide 64

Slide 64 text

多相アロケータ アロケータおさらい 64 自動だった場合の例 // カスタムアロケータ MyAlloc のオブジェクト a MyAlloc a; // カスタムアロケータ MyAlloc を使った文字列型 MyStr using MyStr = std::basic_string, MyAlloc>; // MyStr を格納する、アロケータオブジェクト a を使ったvector std::vector> v(a); // アロケータオブジェクト a を使った要素格納 v.emplace_back("first element"); v.emplace_back("second element");

Slide 65

Slide 65 text

多相アロケータ アロケータおさらい 65 ほんのちょっとの違いに見えるけど、 コードが離れていたり、ネストが深かっ たり(string の vector の map みたいな) すると、かなり厳しい…

Slide 66

Slide 66 text

多相アロケータ アロケータおさらい 66 そこで、アロケータの construct メンバ関数に 小細工する

Slide 67

Slide 67 text

多相アロケータ アロケータおさらい 67 普通の場合 a.construct(addr, args); ⇓ ::new((void*)addr) T(std::forward(args)...); 至って普通の配置new…

Slide 68

Slide 68 text

多相アロケータ アロケータおさらい 68 要素に伝搬させたいアロケータの場合 a.construct(addr, args); ⇓ ::new((void*)addr) T(std::allocator_arg, a, std::forward(args)...); または ::new((void*)addr) T(std::forward(args)..., a); コンストラクタ引数の最初、あるいは最後に自分自身を紛れ込ませる (利用者はアロケータは明示的には指定しない) ちなみに、std::allocator_arg は引数の先頭がアロケータであることを示す タグ std::allocator_arg_t 型のダミーオブジェクト

Slide 69

Slide 69 text

多相アロケータ アロケータおさらい 69 ただし、アロケータが自分を要素に伝搬させたい場合でも ① そもそも要素がアロケータを使う必要が無い(int とか) ② アロケータは使用するけど型が違っている ③ コンストラクタでアロケータを指定できない などの場合があるので、その場合は普通に構築したい。 そこで、std::uses_allocator と言う型トレイツで 伝搬させるかどうかを制御できるようにする。

Slide 70

Slide 70 text

多相アロケータ アロケータおさらい 70 std::uses_allocator T 型のオブジェクトが Alloc 型のアロケータを使用した構築を サポートしているかどうかを判別する型トレイツ。 デフォルトでは T::allocator_type と言うメンバ型が定義されて いて、かつ std::is_convertible_v が true であれば、std::true_type から派生、さもなければ std::false_type から派生。 T::allocator_type と言うメンバ型が定義されていなくても T が Alloc 型のアロケータを使用した構築をサポートしているので あれば std::true_type から派生するように特殊化しておく。

Slide 71

Slide 71 text

多相アロケータ アロケータおさらい 71 uses-allocator 構築 obj を構築対象の T 型のオブジェクト、v1, v2, …, vN をそれぞれ V1, V2, …, VN 型 のコンストラクタ引数、alloc を Alloc 型のアロケータとすると… • std::uses_allocator_v が false で、かつ std::is_constructible_v が true なら、obj(v1, v2, ..., vN) として構築 • そうでなくて、std::uses_allocator_v が true で、かつ std::is_constructible_v が true な ら、obj(std::allocator_arg, alloc, v1, v2, ..., vN) として構築 • そうでなくて、std::uses_allocator_v が true で、かつ std::is_constructible_v が true なら、obj(v1, v2, ..., vN, alloc) として構築 • 上記のいずれでもない場合、ill-formed

Slide 72

Slide 72 text

多相アロケータ アロケータおさらい 72 uses-allocator 構築をサポートするアロケータ • std::polymorphic_allocator • std::scoped_allocator_adaptor ただし、scoped_allocator_adaptor 自体は本物のアロケータではないので (名前の通り、アダプタ)、実際の処理は uses-allocator 構築とはちょっと違う。 あと、scoped_allocator_adaptor と polymorphic_allocator は組み合わせては使えないと思う(多分…)

Slide 73

Slide 73 text

多相アロケータ アロケータおさらい 73 uses-allocator 構築をサポートしている型 • std::array 以外の標準コンテナ • 標準コンテナアダプタ(std::stackとか) • std::basic_string • std::tuple(自分はアロケータ使わないけど※1) • std::match_results(正規表現の検索結果、一部※2) • std::promise(スレッド系、一部※2) ※1 全ての要素型に対して、uses-allocator 構築を行う ※2 引数がアロケータだけのコンストラクタのみ存在

Slide 74

Slide 74 text

多相アロケータ アロケータおさらい 74 tuple があるのに何で pair が無いの?

Slide 75

Slide 75 text

多相アロケータ アロケータおさらい 75 tuple があるのに何で pair が無いの? ⇓ コンストラクタが多すぎると言う理由で、pair の コンストラクタからアロケータを使用するヤツが 削除されてしまった。

Slide 76

Slide 76 text

多相アロケータ アロケータおさらい 76 tuple があるのに何で pair が無いの? ⇓ コンストラクタが多すぎると言う理由で、pair の コンストラクタからアロケータを使用するヤツが 削除されてしまった。 ⇓ pair の各要素を uses-allocator 構築するために は、各アロケータで特別対応する必要がある polymorphic_allocator と scoped_allocator_adaptor は特別対応している

Slide 77

Slide 77 text

多相アロケータ アロケータおさらい 77 勉強会での光成さん(@herumi)からの質問 自作アロケータで uses-allocator 構築する場合、 pair に特別対応しなかったらコンテナの要素に pair 使うとエラーになる? ⇓ ならないです。ただし、コンテナのアロケータは pair の各要素には伝搬しなくなります。 勉強会での 話を基に追加

Slide 78

Slide 78 text

多相アロケータ アロケータおさらい 78 残念なお知らせ C++17 で追加された超有能三兄弟、 std::optional、std::variant、std::any は uses-allocator 構築をサポートしていない。 したがって、コンテナの要素がこの兄弟の場合、 コンテナのアロケータはこの兄弟の要素には 伝搬しない。 わりと悲しい… 当日スライドに入れ 忘れてて勉強会で 口頭補足したやつ

Slide 79

Slide 79 text

多相アロケータ アロケータおさらい 79 余談 • std::shared_ptr はアロケータを使用するが、uses- allocator 構築はサポートしない。 • std::function、std::packaged_task は、C++14 までは uses-allocator 構築をサポートしていることになって いたが、いろいろ問題があったらしく、C++17 からそ もそもアロケータ自体をサポートしなくなった。

Slide 80

Slide 80 text

多相アロケータ アロケータおさらい 80 アロケータのポイント➂ コピー構築、コピー代入、ムーブ代入、 スワップ時のアロケータ伝搬を 制御可能

Slide 81

Slide 81 text

多相アロケータ アロケータおさらい 81 uses-allocator 構築では、construct メンバ関数で コンテナの要素に対するアロケータ伝搬を制御 したが、コピー構築、コピー代入、ムーブ代入、 スワップ時のアロケータ伝搬もメンバの定義に よって制御できるようになっている。 ちなみに、ムーブ構築の場合は問答無用で伝搬する

Slide 82

Slide 82 text

多相アロケータ アロケータおさらい 82 コピー構築時の制御 コピー構築時には、コピー元アロケータの以 下のメンバ関数を呼び出してアロケータオブ ジェクトを取得する。 現在 API 名最長選手権 C++ 標準ライブラリ 同率 6 位※ Alloc select_on_container_copy_construction() const; ※ API名最長選手権 in C++ by yohhoy 先生 参照

Slide 83

Slide 83 text

多相アロケータ アロケータおさらい 83 コピー代入時の制御 コピー代入時には、以下のメンバ型が true_type だった場合にはコピー元アロケータ を、false_type(デフォルト)だった場合にはデ フォルト構築されたアロケータを使用する。 現在 API 名最長選手権 C++ 標準ライブラリ 同率 3 位※ propagate_on_container_copy_assignment ※ API名最長選手権 in C++ by yohhoy 先生 参照

Slide 84

Slide 84 text

多相アロケータ アロケータおさらい 84 ムーブ代入時の制御 ムーブ代入時には、以下のメンバ型が true_type だった場合にはムーブ元アロケー タを、false_type(デフォルト)だった場合には デフォルト構築されたアロケータを使用する。 現在 API 名最長選手権 C++ 標準ライブラリ 同率 3 位※ propagate_on_container_move_assignment ※ API名最長選手権 in C++ by yohhoy 先生 参照

Slide 85

Slide 85 text

多相アロケータ アロケータおさらい 85 スワップ時の制御 スワップ時には、以下のメンバ型が true_type だった場合にはアロケータもスワッ プし、false_type(デフォルト)だった場合には アロケータはスワップしない。 propagate_on_container_swap

Slide 86

Slide 86 text

多相アロケータ アロケータおさらい 86 勉強会での光成さん(@herumi)からの質問 スワップ時にアロケータをスワップしない 選択肢ってあるの? ⇓ 無いです。もしあるとすると、もともと同じアロケータ だった場合。アロケータが違うのにスワップしない 場合は、みんな大好き未定義動作になります。 つまり、その場合コンテナのスワップはやっちゃダメ、 ということです。 勉強会での 話を基に追加

Slide 87

Slide 87 text

多相アロケータ アロケータおさらい 87 ちなみに、多相アロケータは select_on_container_copy_construction は 自分のコピーじゃなく新たなデフォルト構築された アロケータを返し、propagate_on_container_* は 全部 false_type です。 つまり、コピー構築、コピー・ムーブ代入、スワップ ではアロケータ絶対伝搬させないマンです。 このあたりは、提案者の哲学(アロケータは構築時に 一旦設定したら変更されるべきではない)が 反映されているのではないかと思います。 勉強会での 話を基に追加

Slide 88

Slide 88 text

多相アロケータ アロケータおさらい 88 アロケータのポイント④ メモリ割当・解放の際の「ポインタ」は T* じゃなくてもよい。 A::pointer p = a.allocate(1); a.deallocate(p, 1);

Slide 89

Slide 89 text

多相アロケータ アロケータおさらい 89 アロケータのポイント④ ただし、コンストラクタ、デストラクタ 呼び出しの際は普通のポインタ a.construct(std::addressof(*p), args...); a.destroy(std::addressof(*p));

Slide 90

Slide 90 text

多相アロケータ アロケータおさらい 90 T* じゃない「ポインタ」は 「ファンシーポインタ」と呼ばれる。 ただし、ファンシーポインタへの 道のりは厳しい…

Slide 91

Slide 91 text

多相アロケータ アロケータおさらい 91 ファンシーポインタの要件 ① NullablePointer 要件を満たすこと ② コンストラクタ、比較演算子、コピー、ムーブ、スワップの処理は例 外を投げないこと ③ ランダムアクセスイテレータの要件を満たすこと ④ 連続イテレータの要件を満たすこと ①、➂、④は更にそれぞれ厳しい要件がある。 標準にはこの要件を満たすファンシーポインタは存在しない。 (shared_ptr とか unique_ptr は要件を満たさない)

Slide 92

Slide 92 text

多相アロケータ アロケータおさらい 92 ファンシーポインタっていつ使うの?

Slide 93

Slide 93 text

多相アロケータ アロケータおさらい 93 ファンシーポインタっていつ使うの? ⇓ 共有メモリとかのように、特殊なメモリを 使用する時に使いたくなるらしい…

Slide 94

Slide 94 text

多相アロケータ アロケータおさらい 94 共有メモリだとプロセス毎にアドレスが異なる可能性があるので、 普通のポインタ使うとデータが壊れる。 そこで、ポインタオブジェクト自身のアドレスとポインタの指す対象オブ ジェクトのアドレスの差分を保持するオフセットポインタが有効。 オフセットポインタ by Thomas Köppe また、光成さん(@herumi)もおっしゃっていたように、 アドレス範囲を制限してすることによってポインタサイズを 小さくするようなファンシーポインタも考えられる。 しかし、std::list などは通常番兵ノードを置くが、番兵ノードはアロケータ でアロケートせずにオブジェクト本体内に配置することが多い。 アロケータでアロケートしていないオブジェクトへのポインタをこの手の ポインタで指すと、問題が発生する。 D0773R1 Towards meaningful fancy pointers 勉強会での 話を基に追加

Slide 95

Slide 95 text

多相アロケータ アロケータおさらい 95 なお、単にポインタをクラス内にラップしただけのクラスで試 したところ、Clang(libc++) も GCC(libstdc++) も少なくとも以 下のような問題で正しくファンシーポインタをサポートしきれ ていないようでした。 Clang:ファンシーポインタのデフォルト構築がヌルポインタ になることに依存している個所がある(そのような要件は無 い) GCC:本物のポインタを引数にするコンストラクタに依存して いる個所がある(そのような要件は無い) 勉強会での 話を基に追加

Slide 96

Slide 96 text

多相アロケータ アロケータおさらい 96 多相アロケータでは、 ファンシーポインタのサポートは していない。(後述)

Slide 97

Slide 97 text

多相アロケータ 97 詳細

Slide 98

Slide 98 text

多相アロケータ 詳細 98 std::pmr::memory_resource 実際のメモリ割当・解放処理 を実装する、全てのクラスの 抽象基底クラス

Slide 99

Slide 99 text

多相アロケータ 詳細 99 class memory_resource { public: virtual ~memory_resource(); // 公開インタフェース(いわゆるNVI)、polymorphic_allocator で使われる。 void* allocate(size_t bytes, size_t alignment = alignof(max_align_t)); void deallocate(void* p, size_t bytes, size_t alignment = alignof(max_align_t)); bool is_equal(const memory_resource& other) const noexcept; private: // 仮想メンバ関数(ここでは純粋仮想)、派生クラスで実装する。 virtual void* do_allocate(size_t bytes, size_t alignment) = 0; virtual void do_deallocate(void* p, size_t bytes, size_t alignment) = 0; virtual bool do_is_equal(const memory_resource& other) const noexcept = 0; };

Slide 100

Slide 100 text

多相アロケータ 詳細 100 void* allocate(size_t bytes, size_t alignment = alignof(max_align_t)); 単に do_allocate を呼ぶだけ。 NVIだからね… virtual void* do_allocate(size_t bytes, size_t alignment) = 0; サイズ bytes、アラインメント alignment の メモリを割り当てる。 各具象派生クラスで実装する。

Slide 101

Slide 101 text

多相アロケータ 詳細 101 void deallocate(void* p, size_t bytes, size_t alignment = alignof(max_align_t)); 単に do_deallocate を呼ぶだけ。 NVIだ(ry virtual void do_deallocate(void* p, size_t bytes, size_t alignment) = 0; do_allocate で割り当てられたメモリ p を解放する。 サイズ bytes、アラインメント alignment は 割り当て時の値(正しく使われれば)。 各具象派生クラスで実装する。

Slide 102

Slide 102 text

多相アロケータ 詳細 102 bool is_equal(const memory_resource& other) const noexcept; 単に do_is_equal を呼ぶだけ。 N(ry virtual bool do_is_equal(const memory_resource& other) const noexcept = 0; *this で割り当てられたメモリを other で解放したり、 other で割り当てられたメモリを *this で解放したり できる場合のみ true を返す。 各具象派生クラスで実装する。 ふつうは this == &other で良いはず。

Slide 103

Slide 103 text

多相アロケータ 詳細 103 ポインタが void* であることに注意! (アロケータはメンバ型 pointer) ⇓ ファンシーポインタのサポートは 諦めた、とのこと…※ クラス継承構造だからね、仕方ないね… (派生クラスでメンバ関数の戻り値型変更は無理) ※ CppCon 2017: Alisdair Meredith “An allocator model for std2” 32:37

Slide 104

Slide 104 text

多相アロケータ 詳細 104 polymorphic_allocator メンバ関数

Slide 105

Slide 105 text

多相アロケータ 詳細 105 コンストラクタ // デフォルトコンストラクタ。memory_resource はシステムデフォルトを使用(後述) polymorphic_allocator() noexcept; // memory_resource へのポインタからの暗黙変換コンストラクタ。 // アロケータが必要な個所で memory_resource が直接使えるようになるので便利。 polymorphic_allocator(memory_resource*); // コピーコンストラクタ。other の memory_resource をコピーして使用。 polymorphic_allocator(const polymorphic_allocator& other) = default; template polymorphic_allocator(const polymorphic_allocator& other) noexcept; 見ての通り、ムーブコンストラクタは無いよ! (memory_resource のハンドラだからムーブされると困る…)

Slide 106

Slide 106 text

多相アロケータ 詳細 106 代入演算子など // 代入演算子は無い!(コピー代入もムーブ代入も出来ない) polymorphic_allocator& operator=(const polymorphic_allocator& rhs) = delete; // コンテナをコピーする際のアロケータ取得時に呼ばれるメンバ関数。 // デフォルト構築された新たな polymorphic_allocator オブジェクトを返す。 // ちなみに、自分自身を返すのが、アロケータのデフォルトの挙動。 polymorphic_allocator select_on_container_copy_construction() const;

Slide 107

Slide 107 text

多相アロケータ 詳細 107 メモリ割当・解放など // メモリ割当。 memory_resource の allocate 呼び出してキャストして返すだけ。 // サイズは n * sizeof(Tp)、アラインメントは alignof(Tp)。 Tp* allocate(size_t n); // メモリ解放。 memory_resource の deallocate 呼び出すだけ。 // サイズは n * sizeof(Tp)、アラインメントは alignof(Tp)。 void deallocate(Tp* p, size_t n); // memory_resource の取得。 // 単にコンストラクタで設定した memory_resource を返すだけ。 memory_resource* resource() const;

Slide 108

Slide 108 text

多相アロケータ 詳細 108 オブジェクト構築 // コンテナ内でのオブジェクト構築。(emplace 的なヤツ) // 引数 p で指定された場所に、T 型のオブジェクトを、 // *this と std::forward(args)… で uses-allocator 構築する。 // T が std::pair の特殊化の場合は使用されない template void construct(T* p, Args&&... args);

Slide 109

Slide 109 text

多相アロケータ 詳細 109 オブジェクト構築(続き) // 要素型が pair の場合の特別なオブジェクト構築 // pair の first と second をそれぞれうまい具合に uses-allocator 構築する template void construct(pair* p, piecewise_construct_t, tuple x, tuple y); template void construct(pair* p); template void construct(pair* p, U&& x, V&& y); template void construct(pair* p, const pair& pr); template void construct(pair* p, pair&& pr);

Slide 110

Slide 110 text

多相アロケータ 詳細 110 オブジェクト破棄 // オブジェクト破棄。単にデストラクタ呼ぶだけ。 template void destroy(T* p);

Slide 111

Slide 111 text

多相アロケータ 詳細 111 その他 // 使用しているメモリリソースの取得。 memory_resource* resource() const;

Slide 112

Slide 112 text

多相アロケータ 詳細 112 その他(非メンバ関数) // アロケータの比較。*a.resource() == *b.resource() の結果を返すだけ。 template bool operator==(const polymorphic_allocator& a, const polymorphic_allocator& b) noexcept; // アロケータの比較。*a.resource() != *b.resource() の結果を返すだけ。 template bool operator!=(const polymorphic_allocator& a, const polymorphic_allocator& b) noexcept;

Slide 113

Slide 113 text

多相アロケータ 113 標準ライブラリが提供している memory_resource の具象クラス

Slide 114

Slide 114 text

多相アロケータ memory_resource の具象クラス 114 // グローバルオブジェクト(クラス名は未規定) memory_resource* new_delete_resource() noexcept; memory_resource* null_memory_resource() noexcept; // 特殊用途用クラス class synchronized_pool_resource; class unsynchronized_pool_resource; class monotonic_buffer_resource;

Slide 115

Slide 115 text

多相アロケータ memory_resource の具象クラス 115 標準が提供している memory_resource の具象クラス① memory_resource* new_delete_resource() noexcept; 普通に ::operator new、::operator delete でメモリ割 当、解放するヤツ。つまり極めて普通。 この関数を呼ぶと、常に同じオブジェクトへのポイ ンタが返る。(自分でインスタンス生成とかしない)

Slide 116

Slide 116 text

多相アロケータ memory_resource の具象クラス 116 標準が提供している memory_resource の具象クラス② memory_resource* null_resource() noexcept; メモリ割当しようとすると常に bad_alloc 投げるヤツ。 つまり極めて変。他のと組み合わせて使うと有用。 この関数を呼ぶと、常に同じオブジェクトへのポイ ンタが返る。(自分でインスタ(ry

Slide 117

Slide 117 text

多相アロケータ memory_resource の具象クラス 117 標準が提供している memory_resource の具象クラス➂ class synchronized_pool_resource; メモリプールを使用するヤツ。実際に使用するメモ リは上位メモリリソースから割り当てられる。 外部同期無しで複数スレッドから同時使用可能。

Slide 118

Slide 118 text

多相アロケータ memory_resource の具象クラス 118 synchronized_pool_resource イメージ サイズ ~16 17~32 33~64 65~128 プールはブロックサイ ズ毎に分かれている。 (ただし、上限あり) メモリ割当は指定さ れたサイズ以上で最 小のブロックサイズ のプールから。 各プールのメモリは、同一サイズのブロックを複数まとめたチャ ンクに分割されている。プール内のメモリを使い果たすと、新た なチャンクを上位メモリリソースから確保する。その際、チャンク のサイズは等比級数的に増大していく。(ただし、上限あり) 割当済 未割当 次に上位から確保

Slide 119

Slide 119 text

多相アロケータ memory_resource の具象クラス 119 なお、synchronized_pool_resource では、スレッド間の同期コストを削 減するために、スレッド毎にメモリ プールがあるかもしれない… 無いかもしれない… でもスレッド毎にプール持ったら、違うスレッドから解放された場合どうするんだろう…

Slide 120

Slide 120 text

多相アロケータ memory_resource の具象クラス 120 synchronized_pool_resource コンストラクタ① synchronized_pool_resource( const pool_options& opts, memory_resource* upstream); 引数のいずれか、あるいは両方を省いたバージョンもある。(引数1つの バージョンは explicit コンストラクタ) opts:挙動調整用のオプション。後述。 upstream:上位メモリリソースへのポインタ。このメモリリソースから割り 当てられるメモリは全て upstream から取得される。指定しないとデフォ ルトメモリリソース(後述)を指定したことになる。

Slide 121

Slide 121 text

多相アロケータ memory_resource の具象クラス 121 struct pool_options { size_t max_blocks_per_chunk = 0; size_t largest_required_pool_block = 0; }; max_blocks_per_chunk:チャンクの最大ブロック数。チャンクサイズは、各プール のメモリが枯渇して上位メモリリソースに確保に行くたびに等比級数的に増加し ていくが、増加はここで指定したブロック数を上限とする。 largest_required_pool_block:プールするメモリサイズの上限。この値を超えるメ モリ割り当て要求は上位メモリリソースから直接割当し、プールでの管理は行わ ない。 いずれも、0(デフォルト値)を指定すると実装依存の上限値となる。また、指定 値がデカすぎる場合も適当に調整される。

Slide 122

Slide 122 text

多相アロケータ memory_resource の具象クラス 122 synchronized_pool_resource コンストラクタ② コピーコンストラクタ、ムーブコンストラクタは無い。 ちなみに、コピー代入演算子、ムーブ代入演算子も 無い。 つまり、コピー、ムーブは一切できない。

Slide 123

Slide 123 text

多相アロケータ memory_resource の具象クラス 123 synchronized_pool_resource メンバ関数① void release(); 管理している全てのメモリを、たとえ deallocate が 呼び出されていなくても、上位メモリリソースに返す。

Slide 124

Slide 124 text

多相アロケータ memory_resource の具象クラス 124 synchronized_pool_resource メンバ関数② pool_options options() const; プールのオプションを返す。ただし、デフォルト値 0 が実際の実装依存の値になったり、指定された値 が実装によって調整されたりする可能性があるの で、コンストラクタで指定された値とは違うかもしれ ない。

Slide 125

Slide 125 text

多相アロケータ memory_resource の具象クラス 125 synchronized_pool_resource メンバ関数➂ memory_resource* upstream_resource() const; 上位メモリリソースへのポインタを返す。極めて普 通。

Slide 126

Slide 126 text

多相アロケータ memory_resource の具象クラス 126 synchronized_pool_resource 仮想関数① void* do_allocate( size_t bytes, size_t alignment) override; 指定されたサイズ、指定されたアラインメントのメモ リをプールから、あるいは上位メモリリソースから直 接割り当てる。極めて普通。

Slide 127

Slide 127 text

多相アロケータ memory_resource の具象クラス 127 synchronized_pool_resource 仮想関数② void do_deallocate(void* p, size_t bytes, size_t alignment) override; 割り当てられたメモリをプールに返す。 なお、上位メモリリソースの deallocate が呼び出さ れるか否かは未規定。

Slide 128

Slide 128 text

多相アロケータ memory_resource の具象クラス 128 synchronized_pool_resource 仮想関数➂ bool do_is_equal(const memory_resource& other) const noexcept override; 指定されたメモリリソースが自分と互換性のあるメ モリリソースかどうかを返す。 ゆうても、単に this == &other の結果を返すだけ。

Slide 129

Slide 129 text

多相アロケータ memory_resource の具象クラス 129 synchronized_pool_resource デストラクタ ~synchromized_pool_resource(); release() メンバ関数を呼ぶ。つまり、全メモリが解 放される。まぁ当たり前っちゃあ当たり前。 このメモリリソースから割り当てられたメモリを使用 しているコンテナ等よりも、このメモリリソースの生 存期間が短くならないように注意が必要。

Slide 130

Slide 130 text

多相アロケータ memory_resource の具象クラス 130 標準が提供している memory_resource の具象クラス④ class unsynchronized_pool_resource; synchronized_pool_resource のシングルスレッド版。 外部同期無しで複数スレッドから同時使用できない こと以外は synchronized_pool_resource と一緒。 なので説明(ry

Slide 131

Slide 131 text

多相アロケータ memory_resource の具象クラス 131 標準が提供している memory_resource の具象クラス➄ class monotonic_buffer_resource; メモリ割当するだけして、絶対解放しないヤツ。 つまりメモリ使用量が単調(monotonic)増加。 同期とかガチ無視なので、まぁ単一スレッド用。 使用用途が限られるがちょっぱや。

Slide 132

Slide 132 text

多相アロケータ memory_resource の具象クラス 132 monotonic_buffer_resource イメージ メモリは端から順に割り当て ていくだけ。解放は無視。 足りなくなったら上位メモリリ ソースから新たなメモリを確 保するが、その際のサイズ は等比級数的に増加する。 割当済 未割当 次に上位から確保 未割当ポインタ

Slide 133

Slide 133 text

多相アロケータ memory_resource の具象クラス 133 monotonic_buffer_resource コンストラクタ① monotonic_buffer_resource( size_t initial_size, memory_resource* upstream); 引数のいずれか、あるいは両方を省いたバージョンもある。(引数1つの バージョンは explicit コンストラクタ) initial_size:最初に上位メモリリソースから確保するメモリのサイズ。実 装によって調整が入るかもしれない。指定が無い場合は実装依存。 upstream:上位メモリリソースへのポインタ。このメモリリソースから割り 当てられるメモリは全て upstream から取得される。指定しないとデフォ ルトメモリリソース(後述)を指定したことになる。

Slide 134

Slide 134 text

多相アロケータ memory_resource の具象クラス 134 monotonic_buffer_resource コンストラクタ② monotonic_buffer_resource(void* buffer, size_t buffer_size, memory_resource* upstream); upstream を省いたバージョンもある。 buffer:初期の割当用メモリ領域。 buffer_size:buffer の容量。また、次に上位メモリリソースから確保するメ モリのサイズは、この値を等比級数的に増加させた値になる。 upstream:上位メモリリソースへのポインタ。buffer を使い果たしたら、 upstream から取得される。指定しないとデフォルトメモリリソース(後述) を指定したことになる。

Slide 135

Slide 135 text

多相アロケータ memory_resource の具象クラス 135 monotonic_buffer_resource コンストラクタ➂ コピーコンストラクタ、ムーブコンストラクタは無い。 ちなみに、コピー代入演算子、ムーブ代入演算子も 無い。 つまり、コピー、ムーブは一切できない。

Slide 136

Slide 136 text

多相アロケータ memory_resource の具象クラス 136 monotonic_buffer_resource メンバ関数① void release(); 管理している全てのメモリを、たとえ deallocate が 呼び出されていなくても、上位メモリリソースに返す。

Slide 137

Slide 137 text

多相アロケータ memory_resource の具象クラス 137 monotonic_buffer_resource メンバ関数② memory_resource* upstream_resource() const; 上位メモリリソースへのポインタを返す。極めて普 通。

Slide 138

Slide 138 text

多相アロケータ memory_resource の具象クラス 138 monotonic_buffer_resource 仮想関数① void* do_allocate( size_t bytes, size_t alignment) override; 指定されたサイズ、指定されたアラインメントのメモ リを割り当てる。極めて普通。

Slide 139

Slide 139 text

多相アロケータ memory_resource の具象クラス 139 monotonic_buffer_resource 仮想関数② void do_deallocate(void* p, size_t bytes, size_t alignment) override; メモリを解放?いや、そんなことはしない。 とにかく何もしない。

Slide 140

Slide 140 text

多相アロケータ memory_resource の具象クラス 140 monotonic_buffer_resource 仮想関数➂ bool do_is_equal(const memory_resource& other) const noexcept override; 指定されたメモリリソースが自分と互換性のあるメ モリリソースかどうかを返す。 ゆうても、単に this == &other の(ry

Slide 141

Slide 141 text

多相アロケータ memory_resource の具象クラス 141 monotonic_buffer_resource デストラクタ ~monotonic_buffer_resource(); release() メンバ関数を呼ぶ。つまり、全メモリが解 放される。まぁ当たり前っちゃあ当たり前。 このメモリリソースから割り当て(ry

Slide 142

Slide 142 text

多相アロケータ 142 その他のユーティリティ

Slide 143

Slide 143 text

多相アロケータ その他 143 グローバルなユーティリティ① memory_resource* set_default_resource(memory_resource* r) noexcept; デフォルトのメモリリソースを引数 r に設定して、今 まで設定されてたメモリリソースを返す。 r が nullptr なら new_delete_resource を設定する。 ちなみに、システムの初期状態は new_delete_resource になっている。 普通っぽい…

Slide 144

Slide 144 text

多相アロケータ その他 144 グローバルなユーティリティ② memory_resource* get_default_resource() noexcept; 現在のデフォルトのメモリリソースを取得する。

Slide 145

Slide 145 text

多相アロケータ その他 145 グローバルなユーティリティ➂ bool operator==(const memory_resource& a, const memory_resource& b) noexcept; メモリリソースを比較する。 単に &a == &b || a.is_equal(b) を返すだけ。 こいつの引数はポインタじゃないので注意。

Slide 146

Slide 146 text

多相アロケータ その他 146 グローバルなユーティリティ④ bool operator!=(const memory_resource& a, const memory_resource& b) noexcept; メモリリソースを比較する。 みんなの予想通りの挙動。(たぶん) こいつの引数もポインタじゃない。

Slide 147

Slide 147 text

多相アロケータ 147 デメリット

Slide 148

Slide 148 text

多相アロケータ デメリット 148 • アロケータオブジェクト毎に memory_resource へのポインタを持ってい るため、標準アロケータを使った時より各 オブジェクトがポインタ 1 つ分大きくなる。 • メモリ割当、解放が仮想関数になっている ので、標準アロケータを使った時より関数 呼び出しオーバーヘッドが大きくなる。

Slide 149

Slide 149 text

多相アロケータ 149 悲報

Slide 150

Slide 150 text

多相アロケータ 悲報 150 GCC も Clang もまだ多相アロ ケータの実装が終わってない… MSVC2017 はぱっと見ありそうでした… (ただし、どれもちゃんと調査できてない…) 勉強会時コメント by いなむ先生(@いなむのみたま) MSVC2017はちゃんと動くよ!

Slide 151

Slide 151 text

多相アロケータ 悲報 151 GCC HEAD synchronized_pool_resource と unsynchronized_pool_resource が無い。 その他はありそう… Clang HEAD メモリリソースはグローバルなヤツしかないし、 そもそもまだ experimental。 自分でメモリリソース作るなら一応どっちも行けそう…

Slide 152

Slide 152 text

多相アロケータ 悲報 152 しばらくは Boost.Container 使う しかないね… でも Boost.Container も pool_options がちょっと変だった… なぜかデフォルトコンストラクタだけがあるから、 synchronized_pool_resource のコンストラクタに初期化リストが直接書けない…

Slide 153

Slide 153 text

多相アロケータ 参考資料 153 Allocators@C++11 by Cryolite 先生 C++11 でのアロケータ機能拡充に関する神スライド https://www.slideshare.net/Cryolite/allocator11final A visitor’s guide to C++ allocators by Thomas Köppe アロケータの詳細がまとめられた非常に良い記事 カスタムアロケータやオフセットポインターのサンプル実装へのリンクもある https://rawgit.com/google/cxx-std-draft/allocator-paper/allocator_user_guide.html D0773R1 Towards meaningful fancy pointers ファンシーポインタとその適用限界について考察した非常に良い記事 https://quuxplusone.github.io/draft/fancy-pointers.html 勉強会後に 追記

Slide 154

Slide 154 text

多相アロケータ 参考資料 154 CppCon 2017 でのアロケータに関する講演 Pablo Halpern “Allocators: The Good Parts” https://www.youtube.com/watch?v=v3dz-AKOVL8 Alisdair Meredith “An allocator model for std2” https://www.youtube.com/watch?v=oCi_QZ6K_qk John Lakos “Local ('Arena') Memory Allocators (part 1 of 2)” https://www.youtube.com/watch?v=nZNd5FjSquk John Lakos “Local ('Arena') Memory Allocators (part 2 of 2)” https://www.youtube.com/watch?v=CFzuFNSpycI Bob Steagall “How to Write a Custom Allocator” https://www.youtube.com/watch?v=kSWfushlvB8 勉強会後に 追記

Slide 155

Slide 155 text

多相アロケータ 155 完