この記事では、pmr名前空間のコンポーネントを操作する簡単な例と、ポリモーフィックアロケーターの基礎となる基本的な考え方を示します。
c ++ 17で導入されたポリモーフィックアロケーターの主なアイデアは、静的ポリモーフィズム、つまりテンプレートに基づいて実装された標準アロケーターを改善することです。これらは、標準のアロケーターよりもはるかに使いやすく、さらに、さまざまなアロケーターを使用するときにコンテナーのタイプを維持できるため、実行時にアロケーターを変更できます。
あなたがしたい場合は
std::vector
、特定のメモリアロケータで、あなたはアロケータテンプレートパラメータを使用することができます。
auto my_vector = std::vector<int, my_allocator>();
ただし、問題があります。このベクトルは、デフォルトで定義されているものを含め、アロケーターが異なるベクトルと同じタイプではありません。
このようなコンテナは、デフォルトのコンテナを持つベクトルを必要とする関数に渡すことはできません。また、異なるアロケータタイプを持つ2つのベクトルを同じ変数に割り当てることもできません。たとえば、次のようになります。
auto my_vector = std::vector<int, my_allocator>();
auto my_vector2 = std::vector<int, other_allocator>();
auto vec = my_vector; // ok
vec = my_vector2; // error
ポリモーフィックアロケータには、
memory_resource
動的ディスパッチを使用できるように、インターフェイスへのポインタが含まれています。
メモリを操作する戦略を変更するに
memory_resource
は、アロケータのタイプを維持したまま、インスタンスを置き換えるだけで十分です。これは実行時にも実行できます。それ以外の場合、ポリモーフィックアロケーターは標準のアロケーターと同じルールに従って機能します。
新しいアロケータによって使用される特定のデータタイプは、名前空間にあります
std::pmr
。多形アロケータで動作できる標準コンテナのテンプレート特殊化もあります。
現時点での主な問題の1つは、からの新しいバージョンのコンテナとからの
std::pmr
アナログとの非互換性ですstd
。
メインコンポーネント std::pmr:
std::pmr::memory_resource
— , .- :
virtual void* do_allocate(std::size_t bytes, std::size_t alignment)
,virtual void do_deallocate(void* p, std::size_t bytes, std::size_t alignment)
virtual bool do_is_equal(const std::pmr::memory_resource& other) const noexcept
.
std::pmr::polymorphic_allocator
— ,memory_resource
.new_delete_resource()
null_memory_resource()
«»- :
synchronized_pool_resource
unsynchronized_pool_resource
monotonic_buffer_resource
- ,
std::pmr::vector
,std::pmr::string
,std::pmr::map
. , . -
memory_resource
:
memory_resource* new_delete_resource()
, memory_resource, new delete .memory_resource* null_memory_resource()
free関数は、割り当ての試行ごとにmemory_resource
例外をスローするポインタを返しますstd::bad_alloc
。
これは、オブジェクトがヒープにメモリを割り当てないようにするため、またはテスト目的で役立ちます。
class synchronized_pool_resource : public std::pmr::memory_resource
スレッドセーフな汎用memory_resource実装は、さまざまなサイズのメモリブロックを持つプールのセットで構成されます。
各プールは、同じサイズのメモリのチャンクのコレクションです。class unsynchronized_pool_resource : public std::pmr::memory_resource
シングルスレッドバージョンsynchronized_pool_resource
。class monotonic_buffer_resource : public std::pmr::memory_resource
シングルスレッドの高速memory_resource
な特殊用途は、事前に割り当てられたバッファからメモリを取得しますが、それを解放しません。つまり、拡張することしかできません。
使用例
monotonic_buffer_resource
とpmr::vector
:
#include <iostream>
#include <memory_resource> // pmr core types
#include <vector> // pmr::vector
#include <string> // pmr::string
int main() {
char buffer[64] = {}; // a small buffer on the stack
std::fill_n(std::begin(buffer), std::size(buffer) - 1, '_');
std::cout << buffer << '\n';
std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)};
std::pmr::vector<char> vec{ &pool };
for (char ch = 'a'; ch <= 'z'; ++ch)
vec.push_back(ch);
std::cout << buffer << '\n';
}
プログラム出力:
_______________________________________________________________
aababcdabcdefghabcdefghijklmnopabcdefghijklmnopqrstuvwxyz______
上記の例では
monotonic_buffer_resource
、スタックに割り当てられたバッファで初期化されたを使用しました。このバッファへのポインタを使用して、メモリの内容を簡単に表示できます。
ベクターはプールからメモリを取得しますが、これはスタック上にあるため非常に高速です。メモリが不足すると、グローバル演算子を使用して要求します
new
。この例は、予約された数を超える要素を挿入しようとしたときのベクトルの実装を示しています。この場合、monotonic_buffer
古いメモリは解放されず、大きくなるだけです。
もちろん、
reserve()
再割り当てを最小限に抑えるためにベクトルを呼び出すこともできますが、この例の目的はmonotonic_buffer_resource
、コンテナーが拡張するときにベクトルがどのように変化するかを正確に示すことです。
ストレージ pmr::string
文字列をに格納したい場合はどうなり
pmr::vector
ますか?
重要な機能は、コンテナ内のオブジェクトもポリモーフィックアロケータを使用している場合、メモリ管理のために親コンテナのアロケータを要求することです。
この機能を利用したい場合は、
std::pmr::string
代わりにを使用する必要がありますstd::string
。
我々のように通過するスタック上に予め割り当てられたバッファを用いて一例を検討
memory_resource
するためのstd::pmr::vector std::pmr::string
:
#include <iostream>
#include <memory_resource> // pmr core types
#include <vector> // pmr::vector
#include <string> // pmr::string
int main() {
std::cout << "sizeof(std::string): " << sizeof(std::string) << '\n';
std::cout << "sizeof(std::pmr::string): " << sizeof(std::pmr::string) << '\n';
char buffer[256] = {}; // a small buffer on the stack
std::fill_n(std::begin(buffer), std::size(buffer) - 1, '_');
const auto BufferPrinter = [](std::string_view buf, std::string_view title) {
std::cout << title << ":\n";
for (auto& ch : buf) {
std::cout << (ch >= ' ' ? ch : '#');
}
std::cout << '\n';
};
BufferPrinter(buffer, "zeroed buffer");
std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)};
std::pmr::vector<std::pmr::string> vec{ &pool };
vec.reserve(5);
vec.push_back("Hello World");
vec.push_back("One Two Three");
BufferPrinter(std::string_view(buffer, std::size(buffer)), "after two short strings");
vec.emplace_back("This is a longer string");
BufferPrinter(std::string_view(buffer, std::size(buffer)), "after longer string strings");
vec.push_back("Four Five Six");
BufferPrinter(std::string_view(buffer, std::size(buffer)), "after the last string");
}
プログラム出力:
sizeof(std::string): 32
sizeof(std::pmr::string): 40
zeroed buffer:
_______________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________
after two short strings:
#m### ###n### ##########Hello World######m### ##@n### ##########One Two Three###_______________________________________________________________________________________________________________________________________________________________________________#
after longer string strings:
#m### ###n### ##########Hello World######m### ##@n### ##########One Two Three####m### ###n### ##################________________________________________________________________________________________This is a longer string#_______________________________#
after the last string:
#m### ###n### ##########Hello World######m### ##@n### ##########One Two Three####m### ###n### ##################________#m### ###n### ##########Four Five Six###________________________________________This is a longer string#_______________________________#
この例で注意すべき主なポイントは次のとおりです。
- サイズが。
pmr::string
より大きいstd::string
。これは、memory_resource
;へのポインタが - ベクトルを5つの要素用に予約しているため、4を追加しても再割り当ては発生しません。
- 最初の2行は、ベクトルメモリブロックに対して十分に短いため、追加のメモリ割り当ては発生しません。
- 3行目は長く、バッファ内に個別のメモリチャンクが必要であり、このブロックへのポインタのみがベクターに格納されます。
- 出力からわかるように、「これは長い文字列です」は、バッファのほぼ最後にあります。
- 別の短い文字列を挿入すると、ベクトルのメモリブロックにフォールバックします
比較のために、
std::string
代わりに同じ実験をしてみましょうstd::pmr::string
sizeof(std::string): 32
sizeof(std::pmr::string): 40
zeroed buffer:
_______________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________
after two short strings:
###w# ##########Hello World########w# ##########One Two Three###_______________________________________________________________________________________________________________________________________________________________________________________________#
new 24
after longer string strings:
###w# ##########Hello World########w# ##########One Two Three###0#######################_______________________________________________________________________________________________________________________________________________________________________#
after the last string:
###w# ##########Hello World########w# ##########One Two Three###0#######################________@##w# ##########Four Five Six###_______________________________________________________________________________________________________________________________#
今回は、memory_resourceへのポインターを格納する必要がないため、コンテナー内のアイテムが占めるスペースが少なくなります。
短い文字列は引き続きベクトルメモリブロック内に格納されますが、長い文字列はバッファに入れられません。今回は、デフォルトのアロケータを使用して長い文字列が割り当てられ、その文字列
へのポインタがベクトルメモリブロックに配置されます。したがって、この行は出力に表示されません。
もう一度ベクトル展開について:
プール内のメモリがなくなると、アロケータは演算子を使用してメモリを要求すると述べられています
new()
。
実際、これは完全に真実ではありません-メモリはから要求され
memory_resource
、free関数を使用して返され
std::pmr::memory_resource* get_default_resource()
ますデフォルトでは、この関数はを返し
std::pmr::new_delete_resource()
、演算子を使用してメモリを割り当てますnew()
が、関数を使用して置き換えることができます。
std::pmr::memory_resource* set_default_resource(std::pmr::memory_resource* r)
では、
get_default_resource
によって値を返す場合の例を見てみましょう。デフォルト。
メソッド
do_allocate()
とdo_deallocate()
「alignment」引数を使用することに留意する必要があるためnew()
、アライメントをサポートするC ++ 17バージョンが必要です。
void* lastAllocatedPtr = nullptr;
size_t lastSize = 0;
void* operator new(std::size_t size, std::align_val_t align) {
#if defined(_WIN32) || defined(__CYGWIN__)
auto ptr = _aligned_malloc(size, static_cast<std::size_t>(align));
#else
auto ptr = aligned_alloc(static_cast<std::size_t>(align), size);
#endif
if (!ptr)
throw std::bad_alloc{};
std::cout << "new: " << size << ", align: "
<< static_cast<std::size_t>(align)
<< ", ptr: " << ptr << '\n';
lastAllocatedPtr = ptr;
lastSize = size;
return ptr;
}
ここで、主な例に戻りましょう。
constexpr auto buf_size = 32;
uint16_t buffer[buf_size] = {}; // a small buffer on the stack
std::fill_n(std::begin(buffer), std::size(buffer) - 1, 0);
std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)*sizeof(uint16_t)};
std::pmr::vector<uint16_t> vec{ &pool };
for (int i = 1; i <= 20; ++i)
vec.push_back(i);
for (int i = 0; i < buf_size; ++i)
std::cout << buffer[i] << " ";
std::cout << std::endl;
auto* bufTemp = (uint16_t *)lastAllocatedPtr;
for (unsigned i = 0; i < lastSize; ++i)
std::cout << bufTemp[i] << " ";
プログラムは20個の数字をベクトルに入れようとしますが、ベクトルが大きくなるだけなので、32個のエントリがある予約済みバッファーよりも多くのスペースが必要です。
したがって、ある時点で、アロケータはを介してメモリを要求します
get_default_resource
。これにより、グローバルへの呼び出しが発生しnew()
ます。
プログラム出力:
new: 128, align: 16, ptr: 0xc73b20
1 1 2 1 2 3 4 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 132 0 0 0 0 0 0 0 144 0 0 0 65 0 0 0 16080 199 0 0 16176 199 0 0 16176 199 0 0 15344 199 0 0 15472 199 0 0 15472 199 0 0 0 0 0 0 145 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
コンソールへの出力から判断すると、割り当てられたバッファは16要素のみで十分であり、数値17を挿入すると、演算子を使用して128バイトの新しい割り当てが発生します
new()
。
3行目には、演算子を使用して割り当てられたメモリのブロックが表示されます
new()
。
演算子オーバーライドを使用した上記の例
new()
は、製品ソリューションに適している可能性は低いです。
幸いなことに、インターフェイスの独自の実装を作成することを誰も気にしません
memory_resource
。
必要なのは
- から継承
std::pmr::memory_resource
- メソッドの実装:
do_allocate()
do_deallocate()
do_is_equal()
- 実装を
memory_resource
コンテナに渡します。
それで全部です。以下のリンクから、オープンデーの記録を見ることができます。ここでは、コースプログラム、学習プロセスについて詳しく話し、潜在的な学生からの質問に答えます。
続きを読む