expertenaustausch > comp.lang.* > comp.lang.iso-c++

Thomas Dorner (08.11.2018, 16:47)
Hallo C++ Speziallisten,

ich habe vermutlich ein Verständnisproblem bei der Nutzung der Standard
C++ String Klasse in Verbindung mit einem eigenen allocator. Das Ziel
ist, bestimmte Strings gesondert im Speicher abzulegen, und dabei
ansonsten völlig kompatibel zu den "normalen" Strings zu sein. Mein
minimales Beispiel:

#include <iostream>

class myalloc : public std::allocator<char>
{
template<typename T>
T* allocate(std::size_t n, const void* = 0)
{
std::cout << "myalloc::allocate(" << n << ")\n";
if(100 < n) throw std::bad_alloc();
return static_cast<T*>(&_memory);
}
private:
unsigned char _memory[100];
};
myalloc customAllocator;

int main()
{
std::string mystring(customAllocator);
std::string regularString = "test string\n";
mystring = regularString;
std::cout << mystring;
}

Sowohl GCC (als auch Clang und zapcc auf Wandbox ())
akzeptieren den Code, rufen aber nie die spezielle allocate Methode auf,
sondern nach wie vor die von std::allocator.

Beim Versuch, das Verhalten zu verstehen, bin ich in den Tiefen der GCC
C++ Standardbibliothek (bits/basic_string.tcc, in verschiedenen GCC 4er
und 6er Versionen) über folgende Zeile gestolpert:

void* __place = _Raw_bytes_alloc(__alloc).allocate(__size);

"__alloc" dürfte hier noch mein Allocator sein, aber "_Raw_bytes_alloc "
ist ein typedef des std::allocator in bits/basic_string.h:

typedef typename _Alloc::template rebind<char>::other _Raw_bytes_alloc;

"rebind<char>::other" ist wiederum mehr oder weniger ein typedef auf
sich selbst in bits/allocator.h:

template<typename _Tp1>
struct rebind
{ typedef allocator<_Tp1> other; };
};

Für mich liest sich das so, als ob die Standard C++ String Klasse des
GCC hier vor dem Aufruf der "allocate" Methode einen expliziten Cast auf
die Klasse des Allocator der Datentypdefinition machen, anstatt die
speziell angegebene zu verwenden.

Ist dieses Verhalten standard-konform?
Wenn ja, warum; wo liegt mein Denkfehler?

Danke schon mal im Voraus für die Erklärung,
Stefan Ram (08.11.2018, 20:49)
Thomas Dorner <td-dclic01> writes:
>T* allocate(std::size_t n, const void* = 0)


Hier könnte man statt »= 0« auch schreiben: »= nullptr«.

>std::string mystring(customAllocator);


Nach dieser Zeile bestätigt ein

::std::cout <<( mystring.get_allocator() == customAllocator )<< '\n';

, daß der Spezialallokator im Prinzip angekommen ist.

Aber stackoverflow.com:

|std::string is a typedef of basic_string that already
|explicitly uses the default allocator. There is no way for
|std::string to use a different allocator.
"Mark B"?

(std::string ist ein typedef von basic_string, die bereits
explizit den Standardallokator verwendet. Es gibt keine
Möglichkeit, für std::string, einen anderen Allokator zu
verwenden.)

, cplusplus.com:

|All constructors above support an object of member type
|allocator_type as additional optional argument at the end,
|which for string is not relevant

(Alle Konstruktoren oben unterstützen ein Objekt vom Typ
allocator_type als zusätzliches optionales Argument am Ende,
das für ::std::string nicht relevant ist.)

. Dementsprechend wird meist von »basic_string« ausgegangen,
stackoverflow.com:

|typedef basic_string<char, char_traits<char>,
|my_allocator<char> > my_string;
"GManNickG"?
Markus Schaaf (Gestern, 13:25)
Am 08.11.18 um 15:47 schrieb Thomas Dorner:

> #include <iostream>
> class myalloc : public std::allocator<char>
> {
> template<typename T>
> T* allocate(std::size_t n, const void* = 0)


Die "Vererbung" ist irreführend. std::allocator<> enthält keine
virtuellen Funktionen. Damit kann es keine Basisklasse im Sinne
objekt-orientierter Programmierung sein. Deshalb würde in vielen
Programmier-Richtlinien eine Verwendung als öffentliche Basisklasse
nicht gestattet. (Eine Ausnahme wäre das "Curiously recurring template
pattern".) (Wenn man nur Funktionen wiederverwenden möchte, würde man
privat vererben und Using-Deklarationen benutzen.)

[..]
> int main()
> {
> std::string mystring(customAllocator);


Hier passiert sog. Slicing. Der std::allocator<>-Teil wird kopiert. Das
Verhalten ist danach natürlich das übliche. Dass dieser ganz normale
std::allocator<> durch Kopie eines myalloc entstanden ist, ändert sein
Verhalten nicht. Diese Art Fehler ist übrigens die Motivation für o.g.
Richtlinien.

MfG
Ähnliche Themen