Именованные требования C++: Allocator
Инкапсулирует стратегии доступа/адресации, выделения/освобождения памяти и конструирования/уничтожения объектов.
Каждый компонент стандартной библиотеки, которому может потребоваться выделить или освободить память, от std::string, std::vector и каждого контейнера, кроме std::array(начиная с C++11) и std::inplace_vector(начиная с C++26), до std::shared_ptr и std::function(до C++17), делает это через Allocator: объект типа класса, удовлетворяющего следующим требованиям.
Реализация многих требований к аллокатору необязательна, поскольку все AllocatorAwareContainer обращаются к аллокаторам косвенно через std::allocator_traits, а std::allocator_traits предоставляет реализацию этих требований по умолчанию.
Требования
Пусть заданы
-
T, неконстантный тип, не являющийся ссылочным(до C++11)неконстантный объектный тип(начиная с C++11)(до C++17)объектный тип без cv-квалификаторов(начиная с C++17), -
A, тип Allocator для типаT, -
a, объект типаA, -
B, соответствующий тип Allocator для некоторого объектного типаUбез cv-квалификаторов (полученный посредством переназначенияA), -
b, объект типаB, -
p, значение типа std::allocator_traits<A>::pointer, полученное вызовомstd::allocator_traits<A>::allocate(), -
cp, значение типа std::allocator_traits<A>::const_pointer, полученное преобразованием изp, -
vp, значение типа std::allocator_traits<A>::void_pointer, полученное преобразованием изp, -
cvp, значение типа std::allocator_traits<A>::const_void_pointer, полученное преобразованием изcpили изvp, -
xp, разыменовываемый указатель на некоторый объектный типXбез cv-квалификаторов, -
r, lvalue типаT, полученный выражением*p, -
n, значение типа std::allocator_traits<A>::size_type.
| Идентификатор типа | Тип-алиас | Требования |
|---|---|---|
A::pointer (необязательно) | (не указано)[1] |
|
A::const_pointer (необязательно) | (не указано) |
|
A::void_pointer (необязательно) | (не указано) |
|
A::const_void_pointer (необязательно) | (не указано) |
|
A::value_type |
T | |
A::size_type (необязательно) | (не указано) |
|
A::difference_type (необязательно) | (не указано) |
|
A::template rebind<U>::other(необязательно)[2] |
B |
|
| Выражение | Тип возвращаемого значения | Требования |
|---|---|---|
*p |
T& | |
*cp | constT& |
*cp и *p указывают на один и тот же объект. |
p->m | (как есть) | То же, что (*p).m, если (*p).m определено. |
cp->m | (как есть) | То же, что (*cp).m, если (*cp).m определено. |
static_cast<A::pointer>(vp) | (как есть) |
static_cast<A::pointer>(vp) == p |
static_cast<A::const_pointer>(cvp) | (как есть) |
static_cast<A::const_pointer>(cvp) == cp |
std::pointer_traits<A::pointer>::pointer_to(r) | (как есть) |
| Выражение | Тип возвращаемого значения | Требования |
|---|---|---|
a.allocate(n) |
A::pointer | Выделяет память, подходящую для массива объектов типа T[n], и создает массив, но не конструирует его элементы. Может выбрасывать исключения. Если n == 0, возвращаемое значение не определено. |
a.allocate(n, cvp) (необязательно) | То же, что a.allocate(n), но может неопределенным образом использовать cvp (nullptr или указатель, полученный из a.allocate()) для повышения локальности. |
|
a.allocate_at_least(n) (необязательно) (начиная с C++23) | std::allocation_result <A::pointer> | Выделяет память, подходящую для массива объектов типа T[cnt], и создает массив, но не конструирует его элементы, а затем возвращает {p, cnt}, где p указывает на память, а cnt не меньше n. Может выбрасывать исключения. |
a.deallocate(p, n) | (не используется) | Освобождает память, на которую указывает p; это значение должно быть возвращено предыдущим вызовом allocate или allocate_at_least(начиная с C++23), не аннулированным промежуточным вызовом deallocate. n должно совпадать со значением, ранее переданным в allocate, либо находиться между запрошенным и возвращенным количеством элементов через allocate_at_least (может совпадать с любой из границ)(начиная с C++23). Не выбрасывает исключений. |
a.max_size() (необязательно) |
A::size_type | Наибольшее значение, которое можно передать в A::allocate(). |
a.construct(xp, args...) (необязательно) | (не используется) | Конструирует объект типа X в ранее выделенной памяти по адресу, на который указывает xp, используя args... в качестве аргументов конструктора. |
a.destroy(xp) (необязательно) | (не используется) | Уничтожает объект типа X, на который указывает xp, но не освобождает память. |
| Выражение | Тип возвращаемого значения | Требования |
|---|---|---|
a1 == a2 | bool |
|
a1 != a2 |
|
|
| Объявление | Эффект | Требования |
A a1(a) | Копирующим конструированием создает a1 так, что a1 == a.(Примечание: каждый Allocator также удовлетворяет требованиям CopyConstructible.) |
|
A a1 = a |
||
A a(b) | Конструирует a так, что B(a) == b и A(b) == a.(Примечание: это означает, что все аллокаторы, связанные отношением rebind, сохраняют ресурсы друг друга, например пулы памяти.) |
|
A a1(std::move(a)) | Конструирует a1 так, чтобы оно было равно предыдущему значению a. |
|
A a1 = std::move(a) |
||
A a(std::move(b)) | Конструирует a так, чтобы оно было равно предыдущему значению A(b). |
|
| Идентификатор типа | Тип-алиас | Требования |
A::is_always_equal(необязательно) |
std::true_type или std::false_type либо тип, производный от такого типа. |
|
| Выражение | Тип возвращаемого значения | Описание |
|---|---|---|
a.select_on_container_copy_construction()(необязательно) |
A |
|
| Идентификатор типа | Тип-алиас | Описание |
A::propagate_on_container_copy_assignment(необязательно) |
std::true_type или std::false_type либо тип, производный от такого типа. |
|
A::propagate_on_container_move_assignment(необязательно) |
|
|
A::propagate_on_container_swap(необязательно) |
|
Примечания:
- См. также раздел указатели с дополнительной функциональностью ниже.
-
rebindявляется необязательным (предоставляетсяstd::allocator_traits) только в том случае, если этот аллокатор представляет собой шаблон видаSomeAllocator<T, Args>, гдеArgs— ноль или более дополнительных параметров типа шаблона.
Пусть заданы
-
x1иx2, объекты (возможно, разных) типовX::void_pointer,X::const_void_pointer,X::pointerилиX::const_pointer
x1 и x2 являются эквивалентными по значению значениями указателей тогда и только тогда, когда оба значения x1 и x2 можно явно преобразовать в два соответствующих объекта px1 и px2 типа X::const_pointer, используя последовательность преобразований static_cast, в которой применяются только эти четыре типа, и выражение px1 == px2 вычисляется в true. Пусть заданы
-
w1иw2, объекты типаX::void_pointer
w1 == w2 и w1 != w2 один или оба объекта можно заменить объектом типа X::const_void_pointer, эквивалентным по значению, без изменения семантики. Пусть заданы
-
p1иp2, объекты типаX::pointer
p1 == p2, p1 != p2, p1 < p2, p1 <= p2, p1 >= p2, p1 > p2, p1 - p2 один или оба объекта можно заменить объектом типа X::const_pointer, эквивалентным по значению, без изменения семантики. Приведенные выше требования позволяют сравнивать iterator и const_iterator контейнера Container.
Требования полноты аллокатораТип аллокатора
| (начиная с C++17) |
Состояние-содержащие и не содержащие состояние аллокаторы
Каждый тип Allocator либо содержит состояние, либо не содержит состояния. Как правило, тип аллокатора, содержащий состояние, может иметь неравные значения, обозначающие различные ресурсы памяти, тогда как тип аллокатора без состояния обозначает единственный ресурс памяти.
| Хотя пользовательские аллокаторы не обязаны быть аллокаторами без состояния, вопрос о том, поддерживается ли использование аллокаторов, содержащих состояние, в стандартной библиотеке и каким образом, определяется реализацией. Использование неравных значений аллокатора может привести к ошибкам времени выполнения, определяемым реализацией, или к неопределенному поведению, если реализация не поддерживает такое использование. | (до C++11) |
| Пользовательские аллокаторы могут содержать состояние. Каждый контейнер или другой объект, учитывающий аллокатор, хранит экземпляр предоставленного аллокатора и управляет его заменой посредством | (начиная с C++11) |
Экземпляры типа аллокатора без состояния всегда сравниваются как равные. Типы аллокаторов без состояния обычно реализуются как пустые классы и подходят для оптимизации пустого базового класса.
| Тип-член | (начиная с C++11) |
Указатели с дополнительной функциональностью
Если тип-член pointer не является обычным указателем, его обычно называют «указателем с дополнительной функциональностью». Такие указатели появились для поддержки сегментированных архитектур памяти и сегодня используются для доступа к объектам, размещенным в адресных пространствах, отличных от однородного виртуального адресного пространства, доступного обычным указателям. Пример такого указателя — отображаемый адресонезависимый указатель boost::interprocess::offset_ptr, позволяющий размещать структуры данных на основе узлов, такие как std::set, в общей памяти и файлах, отображаемых в память по разным адресам в каждом процессе. Указатели с дополнительной функциональностью можно использовать независимо от предоставившего их аллокаторас помощью шаблона класса std::pointer_traits(начиная с C++11). Функцию std::to_address можно использовать для получения обычного указателя из указателя с дополнительной функциональностью.(начиная с C++20)
| Использование указателей с дополнительной функциональностью, а также настраиваемых размеров и отличающихся типов в стандартной библиотеке поддерживается условно. Реализации могут требовать, чтобы типы-члены | (до C++11) |
КонцептДля определения объекта-запроса
Концепт /*simple-allocator*/, используемый только для описания, задает минимальные ограничения на использование требования Allocator. | (начиная с C++26) |
Стандартная библиотека
Следующие компоненты стандартной библиотеки удовлетворяют требованиям Allocator:
| аллокатор по умолчанию (шаблон класса) |
|
|
(C++11) | реализует многоуровневый аллокатор для многоуровневых контейнеров (шаблон класса) |
|
(C++17) | аллокатор, поддерживающий полиморфизм времени выполнения на основе std::pmr::memory_resource, с которым он был сконструирован (шаблон класса) |
Примеры
Демонстрирует аллокатор C++11, за исключением [[nodiscard]], добавленного для соответствия стилю C++20.
#include <cstdlib>
#include <iostream>
#include <limits>
#include <new>
#include <vector>
template<class T>
struct Mallocator
{
typedef T value_type;
Mallocator() = default;
template<class U>
constexpr Mallocator(const Mallocator <U>&) noexcept {}
[[nodiscard]] T* allocate(std::size_t n)
{
if (n > std::numeric_limits<std::size_t>::max() / sizeof(T))
throw std::bad_array_new_length();
if (auto p = static_cast<T*>(std::malloc(n * sizeof(T))))
{
report(p, n);
return p;
}
throw std::bad_alloc();
}
void deallocate(T* p, std::size_t n) noexcept
{
report(p, n, 0);
std::free(p);
}
private:
void report(T* p, std::size_t n, bool alloc = true) const
{
std::cout << (alloc ? "Alloc: " : "Dealloc: ") << sizeof(T) * n
<< " bytes at " << std::hex << std::showbase
<< reinterpret_cast<void*>(p) << std::dec << '\n';
}
};
template<class T, class U>
bool operator==(const Mallocator <T>&, const Mallocator <U>&) { return true; }
template<class T, class U>
bool operator!=(const Mallocator <T>&, const Mallocator <U>&) { return false; }
int main()
{
std::vector<int, Mallocator<int>> v(8);
v.push_back(42);
} Возможный вывод:
Alloc: 32 bytes at 0x2020c20 Alloc: 64 bytes at 0x2023c60 Dealloc: 32 bytes at 0x2020c20 Dealloc: 64 bytes at 0x2023c60
Отчеты об ошибках
Следующие отчеты об ошибках, изменяющие поведение, были применены задним числом к ранее опубликованным стандартам C++.
| DR | Применяется к | Опубликованное поведение | Корректное поведение |
|---|---|---|---|
| LWG 179 | C++98 |
pointer и const_pointer не требовалосьсравнивать друг с другом | требуется |
| LWG 199 | C++98 | возвращаемое значение a.allocate(0) было неясным | оно не определено |
|
LWG 258 (N2436) | C++98 | отношение равенства между аллокаторами не требовалось делать рефлексивным, симметричным или транзитивным | требуется рефлексивность, симметричность и транзитивность |
| LWG 274 | C++98 |
T мог быть типом с квалификатором const или ссылочным типом,из-за чего std::allocator мог быть некорректным[1] | такие типы запрещены |
| LWG 2016 | C++11 | операции копирования, перемещения и обмена аллокатора могли выбрасывать исключения при использовании | требуется отсутствие исключений |
| LWG 2081 | C++98 C++11 | аллокаторы не обязаны были поддерживать копирующее присваивание (C++98) и перемещающее присваивание (C++11) | требуется |
| LWG 2108 | C++11 | не существовало способа показать, что аллокатор не содержит состояния |
is_always_equal добавлен |
| LWG 2263 | C++11 | решение вопроса LWG 179 было случайно исключено из C++11 и не было обобщено для void_pointer и const_void_pointer | восстановлено и обобщено |
| LWG 2447 | C++11 |
T мог быть объектным типом с квалификатором volatile | такие типы запрещены |
| LWG 2593 | C++11 | перемещение из аллокатора могло изменить его значение | изменение запрещено |
| P0593R6 | C++98 |
allocate не обязаны были создаватьобъект массива в выделенной ими памяти | требуется |
- Типы-члены
referenceиconst_referenceуstd::allocatorопределены соответственно какT&иconst T&.- Если
Tявляется ссылочным типом,referenceиconst_referenceнекорректны, поскольку невозможно образовать ссылку на ссылку (схлопывание ссылок было введено в C++11; см. раздел схлопывание ссылок). - Если
Tимеет квалификатор const,referenceиconst_referenceсовпадают, а набор перегрузокaddress()некорректен.
- Если
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/named_req/Allocator