C++ benannte Anforderungen: Allocator
Kapselt Strategien für Zugriff/Adressierung, Allokation/Deallokation und Konstruktion/Destruktion von Objekten.
Jede Standardbibliothekskomponente, die Speicher alloziieren oder freigeben muss, von std::string, std::vector und jedem Container, außer std::array(seit C++11) und std::inplace_vector(seit C++26), bis hin zu std::shared_ptr und std::function(bis C++17), tut dies über einen Allocator: ein Objekt eines Klassentyps, das die folgenden Anforderungen erfüllt.
Die Implementierung vieler Allocator-Anforderungen ist optional, da alle AllocatorAwareContainer über std::allocator_traits indirekt auf Allocatoren zugreifen, und std::allocator_traits die Standardimplementierung dieser Anforderungen bereitstellt.
Inhalt |
[bearbeiten] Anforderungen
Gegeben
-
T, ein nicht-const, nicht-Referenz-Typ(bis C++11)nicht-const Objekt-Typ(seit C++11)(bis C++17)cv-unqualified Objekt-Typ(seit C++17), -
A, ein Allocator-Typ für den TypT, - a, ein Objekt vom Typ
A, -
B, der entsprechende Allocator-Typ für einen cv-unqualified Objekt-TypU(wie durch Rebinding vonAerhalten), - b, ein Objekt vom Typ
B, - p, ein Wert vom Typ std::allocator_traits<A>::pointer, erhalten durch Aufruf von std::allocator_traits<A>::allocate(),
- cp, ein Wert vom Typ std::allocator_traits<A>::const_pointer, erhalten durch Konvertierung von p,
- vp, ein Wert vom Typ std::allocator_traits<A>::void_pointer, erhalten durch Konvertierung von p,
- cvp, ein Wert vom Typ std::allocator_traits<A>::const_void_pointer, erhalten durch Konvertierung von cp oder von vp,
- xp, ein dereferenzierbarer Zeiger auf einen cv-unqualified Objekt-Typ
X, - r, ein lvalue vom Typ
Terhalten durch den Ausdruck *p, - n, ein Wert vom Typ std::allocator_traits<A>::size_type.
| Typ-ID | Aliassierter Typ | Anforderungen |
|---|---|---|
A::pointer (optional) |
(nicht spezifiziert)[1] | |
A::const_pointer (optional) |
(nicht spezifiziert) |
|
A::void_pointer (optional) |
(nicht spezifiziert) |
|
A::const_void_pointer (optional) |
(nicht spezifiziert) |
|
A::value_type
|
T
|
|
A::size_type (optional) |
(nicht spezifiziert) |
|
A::difference_type (optional) |
(nicht spezifiziert) |
|
A::template rebind<U>::other(optional)[2] |
B
|
|
| Ausdruck | Rückgabetyp | Anforderungen |
|---|---|---|
| *p | T&
|
|
| *cp | const T& | *cp und *p identifizieren das gleiche Objekt. |
| p->m | (wie es ist) | Gleich wie (*p).m, wenn (*p).m wohldefiniert ist. |
| cp->m | (wie es ist) | Gleich wie (*cp).m, wenn (*cp).m wohldefiniert ist. |
| static_cast<A::pointer>(vp) | (wie es ist) | static_cast<A::pointer>(vp) == p |
| static_cast<A::const_pointer>(cvp) | (wie es ist) | static_cast<A::const_pointer>(cvp) == cp |
| std::pointer_traits<A::pointer>::pointer_to(r) | (wie es ist) |
| Ausdruck | Rückgabetyp | Anforderungen |
|---|---|---|
| a.allocate(n) | A::pointer
|
Allokiert Speicher, der für ein Array-Objekt vom Typ T[n] geeignet ist, und erstellt das Array, konstruiert aber keine Array-Elemente. Kann Ausnahmen auslösen. Wenn n == 0, ist der Rückgabewert nicht spezifiziert. |
| a.allocate(n, cvp) (optional) | Gleich wie a.allocate(n), kann aber cvp (nullptr oder ein von a.allocate() erhaltener Zeiger) auf nicht spezifizierte Weise zur Unterstützung der Lokalität verwenden. | |
| a.allocate_at_least(n) (optional) (seit C++23) | std::allocation_result <A::pointer> |
Allokiert Speicher, der für ein Array-Objekt vom Typ T[cnt] geeignet ist, und erstellt das Array, konstruiert aber keine Array-Elemente. Gibt dann {p, cnt} zurück, wobei p auf den Speicher zeigt und cnt nicht kleiner als n ist. Kann Ausnahmen auslösen. |
| a.deallocate(p, n) | (nicht verwendet) | Gibt den von p gezeigten Speicher frei, welcher ein Wert sein muss, der durch einen vorherigen Aufruf von allocateoder allocate_at_least(seit C++23) zurückgegeben und nicht durch einen dazwischenliegenden Aufruf von deallocate ungültig gemacht wurde. n muss mit dem zuvor an allocate übergebenen Wert übereinstimmenoder zwischen der Anfrage und der von allocate_at_least zurückgegebenen Anzahl von Elementen liegen (kann gleich einem der Grenzen sein)(seit C++23). Löst keine Ausnahmen aus. |
| a.max_size() (optional) | A::size_type
|
Der größte Wert, der an A::allocate() übergeben werden kann. |
| a.construct(xp, args...) (optional) | (nicht verwendet) | Konstruiert ein Objekt vom Typ X im zuvor allokierten Speicher an der von xp gezeigten Adresse unter Verwendung von args... als Konstruktorargumente. |
| a.destroy(xp) (optional) | (nicht verwendet) | Destruiert ein Objekt vom Typ X, auf das von xp gezeigt wird, gibt aber keinen Speicher frei. |
| Ausdruck | Rückgabetyp | Anforderungen |
|---|---|---|
| a1 == a2 | bool |
|
| a1 != a2 |
| |
| Deklaration | Auswirkung | Anforderungen |
| A a1(a) | Kopiert a1, sodass a1 == a gilt. (Hinweis: Jeder Allocator erfüllt auch CopyConstructible.) |
|
| A a1 = a | ||
| A a(b) | Konstruiert a so, dass B(a) == b und A(b) == a gilt. (Hinweis: Dies impliziert, dass alle durch rebind verbundenen Allocatoren die Ressourcen des jeweils anderen, wie z. B. Speicherpools, beibehalten.) |
|
| A a1(std::move(a)) | Konstruiert a1 so, dass sie dem vorherigen Wert von a entspricht. |
|
| A a1 = std::move(a) | ||
| A a(std::move(b)) | Konstruiert a so, dass sie dem vorherigen Wert von A(b) entspricht. |
|
| Typ-ID | Aliassierter Typ | Anforderungen |
A::is_always_equal(optional) |
std::true_type oder std::false_type oder abgeleitet davon. |
|
| Ausdruck | Rückgabetyp | Beschreibung |
|---|---|---|
| a.select_on_container_copy_construction() (optional) |
A
|
|
| Typ-ID | Aliassierter Typ | Beschreibung |
A::propagate_on_container_copy_assignment(optional) |
std::true_type oder std::false_type oder abgeleitet davon. |
|
A::propagate_on_container_move_assignment(optional) |
| |
A::propagate_on_container_swap(optional) |
|
Anmerkungen
- ↑ Siehe auch Fancy-Pointer unten.
- ↑
rebindist nur optional (bereitgestellt durch std::allocator_traits), wenn dieser Allocator eine Vorlage der FormSomeAllocator<T, Args>ist, wobeiArgsnull oder mehr zusätzliche Template-Typ-Parameter sind.
Gegeben
- x1 und x2, Objekte von (möglicherweise verschiedenen) Typen
X::void_pointer,X::const_void_pointer,X::pointeroderX::const_pointer
- Dann sind x1 und x2 äquivalente Zeigerwerte, wenn und nur wenn sowohl x1 als auch x2 explizit in die beiden entsprechenden Objekte px1 und px2 vom Typ
X::const_pointerkonvertiert werden können, unter Verwendung einer Sequenz von static_casts, die nur diese vier Typen verwenden, und der Ausdruck px1 == px2 zu true ausgewertet wird.
Gegeben
- w1 und w2, Objekte vom Typ
X::void_pointer
- Dann können für die Ausdrücke w1 == w2 und w1 != w2 entweder oder beide Objekte durch ein *äquivalentes* Objekt vom Typ
X::const_void_pointerersetzt werden, ohne dass sich die Semantik ändert.
Gegeben
- p1 und p2, Objekte vom Typ
X::pointer
- Dann können für die Ausdrücke p1 == p2, p1 != p2, p1 < p2, p1 <= p2, p1 >= p2, p1 > p2, p1 - p2 entweder oder beide Objekte durch ein *äquivalentes* Objekt vom Typ
X::const_pointerersetzt werden, ohne dass sich die Semantik ändert.
Die obigen Anforderungen ermöglichen den Vergleich der iterators und const_iterators von Container.
Allocator-VollständigkeitsanforderungenEin Allocator-Typ
|
(seit C++17) |
[bearbeiten] Zustandsbehaftete und zustandslose Allocatoren
Jeder Allocator-Typ ist entweder zustandsbehaftet oder zustandslos. Im Allgemeinen kann ein zustandsbehafteter Allocator-Typ ungleiche Werte aufweisen, die unterschiedliche Speicherressourcen bezeichnen, während ein zustandsloser Allocator-Typ eine einzelne Speicherressource bezeichnet.
|
Obwohl benutzerdefinierte Allocatoren nicht zustandsbehaftet sein müssen, ist die Verwendung von zustandsbehafteten Allocatoren in der Standardbibliothek implementierungsabhängig. Die Verwendung ungleicher Allocator-Werte kann zu implementierungsabhängigen Laufzeitfehlern oder undefiniertem Verhalten führen, wenn die Implementierung eine solche Verwendung nicht unterstützt. |
(bis C++11) |
|
Benutzerdefinierte Allocatoren können Zustand enthalten. Jeder Container oder ein anderes Allocator-bewusstes Objekt speichert eine Instanz des bereitgestellten Allocators und steuert den Austausch von Allocatoren über std::allocator_traits. |
(seit C++11) |
Instanzen eines zustandslosen Allocator-Typs sind immer gleich. Zustandlose Allocator-Typen werden typischerweise als leere Klassen implementiert und eignen sich für die Empty Base Class Optimization.
|
Der Mitgliedstyp |
(seit C++11) |
[bearbeiten] Fancy-Pointer
Wenn der Mitgliedstyp pointer kein roher Zeigertyp ist, wird er üblicherweise als "Fancy-Pointer" bezeichnet. Solche Zeiger wurden zur Unterstützung von segmentierten Speicherarchitekturen eingeführt und werden heute verwendet, um auf Objekte zuzugreifen, die in Adressräumen allokiert wurden, die sich vom homogenen virtuellen Adressraum unterscheiden, auf den rohe Zeiger zugreifen. Ein Beispiel für einen Fancy-Pointer ist der Mapping-Adresse-unabhängige Zeiger boost::interprocess::offset_ptr, der es ermöglicht, knotenbasierte Datenstrukturen wie std::set im gemeinsamen Speicher und in speicherabgebildeten Dateien zu allokieren, die in verschiedenen Adressen in jedem Prozess abgebildet sind. Fancy-Pointer können unabhängig von dem Allocator verwendet werden, der sie bereitgestellt hat, über die Klassenvorlage std::pointer_traits(seit C++11). Die Funktion std::to_address kann verwendet werden, um einen rohen Zeiger aus einem Fancy-Pointer zu erhalten.(seit C++20)
|
Die Verwendung von Fancy-Pointern und benutzerdefinierten Größen-/Differenztypen in der Standardbibliothek ist bedingt unterstützt. Implementierungen können verlangen, dass die Mitgliedstypen |
(bis C++11) |
KonzeptFür die Definition des Abfrageobjekts std::get_allocator wird das folgende exposition-only-Konzept definiert.
Das exposition-only-Konzept /*simple-allocator*/ definiert die minimalen Benutzbarkeitsbeschränkungen der Allocator-Anforderung. |
(seit C++26) |
[bearbeiten] Standardbibliothek
Die folgenden Standardbibliothekskomponenten erfüllen die Allocator-Anforderungen
| der Standard-Allocator (Klassen-Template) | |
| (C++11) |
implementiert mehrstufige Allokatoren für mehrstufige Container (Klassen-Template) |
| (C++17) |
ein Allocator, der Laufzeitpolymorphismus basierend auf der std::pmr::memory_resource unterstützt, mit der er konstruiert wird (Klassen-Template) |
[bearbeiten] Beispiele
Demonstriert einen C++11-Allocator, außer für [[nodiscard]], das hinzugefügt wurde, um dem C++20-Stil zu entsprechen.
#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); }
Mögliche Ausgabe
Alloc: 32 bytes at 0x2020c20 Alloc: 64 bytes at 0x2023c60 Dealloc: 32 bytes at 0x2020c20 Dealloc: 64 bytes at 0x2023c60
[bearbeiten] Fehlerberichte
Die folgenden Verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | angewendet auf | Verhalten wie veröffentlicht | Korrigiertes Verhalten |
|---|---|---|---|
| LWG 179 | C++98 | pointer und const_pointer waren nichtverpflichtet, miteinander vergleichbar zu sein |
Gefordert |
| LWG 199 | C++98 | der Rückgabewert von a.allocate(0) war unklar | es ist nicht spezifiziert |
| LWG 258 (N2436) |
C++98 | die Gleichheitsbeziehung zwischen Allocatoren war nicht reflexiv, symmetrisch oder transitiv sein musste |
reflexiv sein musste, symmetrisch und transitiv |
| LWG 274 | C++98 | T konnte ein const-qualifizierter Typ oder Referenztyp sein,wodurch std::allocator möglicherweise fehlerhaft war[1] |
verbannte diese Typen |
| LWG 2016 | C++11 | die Kopier-, Move- und Swap-Operationen von Allocatoren konnten werfend sein, wenn sie verwendet wurden |
nicht werfend sein musste |
| LWG 2081 | C++98 C++11 |
Allocatoren mussten kein Kopieren unterstützen Zuweisung (C++98) und Move-Zuweisung (C++11) |
Gefordert |
| LWG 2108 | C++11 | es gab keine Möglichkeit zu zeigen, dass ein Allocator zustandslos ist | is_always_equal bereitgestellt |
| LWG 2263 | C++11 | die Auflösung von LWG-Problem 179 wurde versehentlich in C++11 fallen gelassen und nicht auf void_pointer und const_void_pointer verallgemeinert |
wiederhergestellt und verallgemeinert |
| LWG 2447 | C++11 | T konnte ein volatile-qualifizierter Objekttyp sein |
verbannte diese Typen |
| LWG 2593 | C++11 | das Verschieben von einem Allocator konnte seinen Wert ändern | Änderung verboten |
| P0593R6 | C++98 | allocate mussten keinArray-Objekt im zugewiesenen Speicher erzeugen |
Gefordert |
- ↑ Die Membertypen
referenceundconst_referencevon std::allocator sind alsT&bzw.const T&definiert.- Wenn
Tein Referenztyp ist, sindreferenceundconst_referencefehlerhaft, da eine Referenz auf eine Referenz nicht gebildet werden kann (Referenzkollaps (Reference collapsing) wurde in C++11 eingeführt). - Wenn
Tconst-qualifiziert ist, sindreferenceundconst_referencegleich, und die Überladungsmenge von address() ist fehlerhaft.
- Wenn