Klassenvorlagen im ::std
Namespace können im Allgemeinen durch Programme für benutzerdefinierte Typen spezialisiert werden. Ich habe keine Ausnahme von dieser Regel für gefunden std::allocator
.
Darf ich mich also auf std::allocator
meine eigenen Typen spezialisieren? Und wenn ich darf, muss ich dann alle Mitglieder der std::allocator
primären Vorlage bereitstellen , da viele von ihnen von bereitgestellt werden können std::allocator_traits
(und daher in C ++ 17 veraltet sind)?
Betrachten Sie dieses Programm
#include<vector>
#include<utility>
#include<type_traits>
#include<iostream>
#include<limits>
#include<stdexcept>
struct A { };
namespace std {
template<>
struct allocator<A> {
using value_type = A;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using propagate_on_container_move_assignment = std::true_type;
allocator() = default;
template<class U>
allocator(const allocator<U>&) noexcept {}
value_type* allocate(std::size_t n) {
if(std::numeric_limits<std::size_t>::max()/sizeof(value_type) < n)
throw std::bad_array_new_length{};
std::cout << "Allocating for " << n << "\n";
return static_cast<value_type*>(::operator new(n*sizeof(value_type)));
}
void deallocate(value_type* p, std::size_t) {
::operator delete(p);
}
template<class U, class... Args>
void construct(U* p, Args&&... args) {
std::cout << "Constructing one\n";
::new((void *)p) U(std::forward<Args>(args)...);
};
template<class U>
void destroy( U* p ) {
p->~U();
}
size_type max_size() const noexcept {
return std::numeric_limits<size_type>::max()/sizeof(value_type);
}
};
}
int main() {
std::vector<A> v(2);
for(int i=0; i<6; i++) {
v.emplace_back();
}
std::cout << v.size();
}
Die Ausgabe dieses Programms mit libc ++ (Clang with -std=c++17 -Wall -Wextra -pedantic-errors -O2 -stdlib=libc++
) ist:
Allocating for 2
Constructing one
Constructing one
Allocating for 4
Constructing one
Constructing one
Allocating for 8
Constructing one
Constructing one
Constructing one
Constructing one
8
und die Ausgabe mit libstdc ++ (Clang with -std=c++17 -Wall -Wextra -pedantic-errors -O2 -stdlib=libstdc++
) ist:
Allocating for 2
Allocating for 4
Constructing one
Constructing one
Allocating for 8
Constructing one
Constructing one
Constructing one
Constructing one
8
Wie Sie sehen können libstdc ++ ehren nicht immer die Überlastung , construct
dass ich zur Verfügung gestellt haben , und wenn ich die entfernen construct
, destroy
oder max_size
Mitglieder, dann wird das Programm nicht einmal mit libstdc ++ beschwerte sich über diese fehlenden Mitglieder kompilieren, obwohl sie geliefert werden std::allocator_traits
.
Hat das Programm ein undefiniertes Verhalten und sind daher beide Standardbibliotheken korrekt, oder ist das Verhalten des Programms genau definiert und die Standardbibliothek für die Verwendung meiner Spezialisierung erforderlich?
Beachten Sie, dass es einige Mitglieder aus std::allocator
der primären Vorlage gibt, die ich in meiner Spezialisierung noch ausgelassen habe. Muss ich sie auch hinzufügen?
Um genau zu sein, habe ich ausgelassen
using is_always_equal = std::true_type
std::allocator_traits
Dies wird von bereitgestellt, da mein Allokator leer ist, aber Teil der std::allocator
Schnittstelle wäre.
Ich verließ auch heraus pointer
, const_pointer
, reference
, const_reference
, rebind
und address
, die alle durch zur Verfügung gestellt werdenstd::allocator_traits
und in C ++ als veraltet 17 für std::allocator
‚s - Schnittstelle.
Wenn Sie der Meinung sind, dass alle diese Elemente so definiert werden müssen, dass sie mit der std::allocator
Benutzeroberfläche übereinstimmen , sollten Sie sie dem Code hinzufügen.
Antworten:
Gemäß 23.2.1 [container.requirements.general] / 3:
Auch gemäß 17.6.4.2.1:
Ich glaube nicht, dass der Standard die Spezialisierung verbietet
std::allocator
, da ich alle Abschnitte gelesen habestd::allocator
und nichts erwähnt habe. Ich habe mir auch angesehen, wie es aussieht, wenn der Standard die Spezialisierung verbietet, und ich habe nichts Vergleichbares gefundenstd::allocator
.Die Anforderungen für
Allocator
sind hier und Ihre Spezialisierung erfüllt sie.Daher kann ich nur schlussfolgern, dass libstdc ++ tatsächlich gegen den Standard verstößt (vielleicht habe ich irgendwo einen Fehler gemacht). Ich habe festgestellt, dass
std::allocator
libstdc ++ , wenn man sich nur spezialisiert , mit der Platzierung new für den Konstruktor reagiert, da sie eine speziell für diesen Fall spezialisierte Vorlage haben, während sie den angegebenen Allokator für andere Operationen verwenden. Der relevante Code ist hier (dies ist innamespace std
;allocator
hier ist::std::allocator
):std::__uninitialized_default_n
Anrufe,std::_Construct
die Platzierung neu verwenden. Dies erklärt, warum in Ihrer Ausgabe "Konstruieren einer" vor "Zuweisen für 4" nicht angezeigt wird.EDIT: Wie OP in einem Kommentar betonte,
std::__uninitialized_default_n
ruftdas hat eigentlich eine spezialisierung wenn
__is_trivial(_ValueType) && __assignable
isttrue
, was ist hier . Es verwendetstd::fill_n
(wovalue
trivial konstruiert ist), anstattstd::_Construct
jedes Element aufzurufen . DaA
es trivial und kopierbar ist, würde es diese Spezialisierung tatsächlich aufrufen. Dies wird natürlich auch nicht verwendetstd::allocator_traits<allocator_type>::construct
.quelle
std::_Construct
Anruf von dem, was ich sagen kann, nicht tatsächlich verwendet , da erA
trivial kopierbar und trivial kopierzuweisbar ist. Daher wird die andere Spezialisierung von__uninitialized_default_n_1
gewählt, diestd::fill_n
stattdessen aufruft und dann erneut anmemcpy
/ sendetmemset
. Ich war mir dessen bewusst, wie es libstdc ++ optimiert, aber ich wusste nicht, dass derstd::allocator::construct
Aufruf auch für nicht triviale Typen übersprungen wird. Es kann also nur ein Versehen sein, wie libstdc ++ identifiziert, dass die von der Bibliothek bereitgestellte Bibliothekstd::allocator
verwendet wird.