Namensräume
Varianten
Aktionen

C++ benannte Anforderungen: Allocator

Von cppreference.com
 
 
C++ benannte Anforderungen
 

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 Typ T,
  • a, ein Objekt vom Typ A,
  • B, der entsprechende Allocator-Typ für einen cv-unqualified Objekt-Typ U (wie durch Rebinding von A erhalten),
  • 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 T erhalten durch den Ausdruck *p,
  • n, ein Wert vom Typ std::allocator_traits<A>::size_type.
Innere Typen
Typ-ID Aliassierter Typ Anforderungen
A::pointer (optional) (nicht spezifiziert)[1]
A::const_pointer (optional) (nicht spezifiziert)
A::void_pointer (optional) (nicht spezifiziert)
  • Erfüllt NullablePointer.
  • A::pointer ist zu A::void_pointer konvertierbar.
  • B::void_pointer und A::void_pointer sind vom gleichen Typ.
A::const_void_pointer (optional) (nicht spezifiziert)
  • Erfüllt NullablePointer.
  • A::pointer, A::const_pointer und A::void_pointer sind zu A::const_void_pointer konvertierbar.
  • B::const_void_pointer und A::const_void_pointer sind vom gleichen Typ.
A::value_type T
A::size_type (optional) (nicht spezifiziert)
  • Ein vorzeichenloser Ganzzahltyp.
  • Kann die Größe des größten Objekts darstellen, das A allokieren kann.
A::difference_type (optional) (nicht spezifiziert)
  • Ein vorzeichenbehafteter Ganzzahltyp.
  • Kann die Differenz zweier Zeiger auf von A allokierte Objekte darstellen.
A::template rebind<U>::other
(optional)[2]
B
  • Für jedes U ist B::template rebind<T>::other gleich A.
Operationen auf Zeigern
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)
Speicher- und Lebensdaueroperationen
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.
Beziehung zwischen Instanzen
Ausdruck Rückgabetyp Anforderungen
a1 == a2 bool
  • true nur, wenn der von dem Allocator a1 allokierte Speicher über a2 freigegeben werden kann.
  • Legt eine reflexive, symmetrische und transitive Beziehung fest.
  • Löst keine Ausnahmen aus.
a1 != a2
  • Gleich wie !(a1 == a2).
Deklaration Auswirkung Anforderungen
A a1(a) Kopiert a1, sodass a1 == a gilt.
(Hinweis: Jeder Allocator erfüllt auch CopyConstructible.)
  • Löst keine Ausnahmen aus.
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.)
  • Löst keine Ausnahmen aus.
A a1(std::move(a)) Konstruiert a1 so, dass sie dem vorherigen Wert von a entspricht.
  • Löst keine Ausnahmen aus.
  • Der Wert von a bleibt unverändert und a1 == a.
A a1 = std::move(a)
A a(std::move(b)) Konstruiert a so, dass sie dem vorherigen Wert von A(b) entspricht.
  • Löst keine Ausnahmen aus.
Typ-ID Aliassierter Typ Anforderungen
A::is_always_equal
(optional)
std::true_type oder std::false_type oder abgeleitet davon.
Einfluss auf Container-Operationen
Ausdruck Rückgabetyp Beschreibung
a.select_on_container_copy_construction()
(optional)
A
  • Stellt eine Instanz von A bereit, die vom Container verwendet werden soll, der aus dem, der a verwendet, kopierkonstruiert wird.
  • (Gibt normalerweise entweder eine Kopie von a oder einen standardkonstruierten A zurück.)
Typ-ID Aliassierter Typ Beschreibung
A::propagate_on_container_copy_assignment
(optional)
std::true_type oder std::false_type oder abgeleitet davon.
  • std::true_type oder davon abgeleitet, wenn der Allocator vom Typ A kopiert werden muss, wenn der Container, der ihn verwendet, kopiert wird.
  • Wenn dieses Mitglied std::true_type oder davon abgeleitet ist, muss A CopyAssignable erfüllen und die Kopiervorgang darf keine Ausnahmen auslösen.
  • Beachten Sie, dass, wenn die Allocatoren des Quell- und Zielcontainers nicht gleich sind, die Kopierzuweisung den Speicher des Ziels mit dem alten Allocator freigeben und dann mit dem neuen Allocator allokieren muss, bevor die Elemente (und der Allocator) kopiert werden.
A::propagate_on_container_move_assignment
(optional)
  • std::true_type oder davon abgeleitet, wenn der Allocator vom Typ A verschoben werden muss, wenn der Container, der ihn verwendet, verschoben wird.
  • Wenn dieses Mitglied std::true_type oder davon abgeleitet ist, muss A MoveAssignable erfüllen und der Verschiebevorgang darf keine Ausnahmen auslösen.
  • Wenn dieses Mitglied nicht angegeben oder von std::false_type abgeleitet ist und die Allocatoren des Quell- und Zielcontainers nicht gleich sind, kann die Verschiebung der Zuweisung keinen Besitz des Quellspeichers übernehmen und muss die Elemente einzeln verschieben oder konstruieren und dabei den eigenen Speicher bei Bedarf anpassen.
A::propagate_on_container_swap
(optional)
  • std::true_type oder davon abgeleitet, wenn die Allocatoren vom Typ A vertauscht werden müssen, wenn zwei Container, die sie verwenden, vertauscht werden.
  • Wenn dieses Mitglied std::true_type oder davon abgeleitet ist, muss der Typ A Swappable erfüllen und der Tauschvorgang darf keine Ausnahmen auslösen.
  • Wenn dieses Mitglied nicht angegeben oder von std::false_type abgeleitet ist und die Allocatoren der beiden Container nicht gleich sind, ist das Verhalten des Container-Tauschs undefiniert.

Anmerkungen

  1. Siehe auch Fancy-Pointer unten.
  2. rebind ist nur optional (bereitgestellt durch std::allocator_traits), wenn dieser Allocator eine Vorlage der Form SomeAllocator<T, Args> ist, wobei Args null 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::pointer oder X::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_pointer konvertiert 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_pointer ersetzt 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_pointer ersetzt werden, ohne dass sich die Semantik ändert.

Die obigen Anforderungen ermöglichen den Vergleich der iterators und const_iterators von Container.

Allocator-Vollständigkeitsanforderungen

Ein Allocator-Typ X für den Typ T erfüllt zusätzlich die Allocator-Vollständigkeitsanforderungen, wenn beide der folgenden Aussagen zutreffen, unabhängig davon, ob T ein vollständiger Typ ist:

  • X ist ein vollständiger Typ.
  • Mit Ausnahme von value_type sind alle Mitgliedstypen von std::allocator_traits<X> vollständige Typen.
(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 is_always_equal von std::allocator_traits wird absichtlich verwendet, um festzustellen, ob ein Allocator-Typ zustandslos ist.

(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 pointer, const_pointer, size_type und difference_type jeweils value_type*, const value_type*, std::size_t und std::ptrdiff_t sind.

(bis C++11)

Konzept

Für die Definition des Abfrageobjekts std::get_allocator wird das folgende exposition-only-Konzept definiert.

template<class Alloc>

concept /*simple-allocator*/ = requires(Alloc alloc, std::size_t n)
{
    { *alloc.allocate(n) } -> std::same_as<typename Alloc::value_type&>;
    { alloc.deallocate(alloc.allocate(n), n) };  
} && std::copy_constructible<Alloc>

  && std::equality_comparable<Alloc>;

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) [edit]
implementiert mehrstufige Allokatoren für mehrstufige Container
(Klassen-Template) [edit]
ein Allocator, der Laufzeitpolymorphismus basierend auf der std::pmr::memory_resource unterstützt, mit der er konstruiert wird
(Klassen-Template) [edit]

[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 nicht
verpflichtet, 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 kein
Array-Objekt im zugewiesenen Speicher erzeugen
Gefordert
  1. Die Membertypen reference und const_reference von std::allocator sind als T& bzw. const T& definiert.
    • Wenn T ein Referenztyp ist, sind reference und const_reference fehlerhaft, da eine Referenz auf eine Referenz nicht gebildet werden kann (Referenzkollaps (Reference collapsing) wurde in C++11 eingeführt).
    • Wenn T const-qualifiziert ist, sind reference und const_reference gleich, und die Überladungsmenge von address() ist fehlerhaft.