Warum brauche ich std :: get_temporary_buffer?

84

Für welchen Zweck sollte ich verwenden std::get_temporary_buffer? Standard sagt folgendes:

Erhält einen Zeiger auf die Speicherung, der ausreicht, um bis zu n benachbarte T-Objekte zu speichern.

Ich dachte, dass der Puffer auf dem Stapel zugewiesen wird, aber das ist nicht wahr. Nach dem C ++ Standard ist dieser Puffer eigentlich nicht temporär. Welche Vorteile hat diese Funktion gegenüber der globalen Funktion ::operator new, die auch die Objekte nicht erstellt ? Habe ich recht, dass die folgenden Aussagen gleichwertig sind?

int* x;
x = std::get_temporary_buffer<int>( 10 ).first;
x = static_cast<int*>( ::operator new( 10*sizeof(int) ) );

Existiert diese Funktion nur für Syntaxzucker? Warum gibt es temporaryin seinem Namen?


Ein Anwendungsfall wurde im Dr. Dobb's Journal vom 1. Juli 1996 für die Implementierung von Algorithmen vorgeschlagen:

Wenn kein Puffer zugewiesen werden kann oder wenn er kleiner als angefordert ist, funktioniert der Algorithmus immer noch korrekt. Er verlangsamt sich lediglich.

Kirill V. Lyadvinsky
quelle
2
Zu Ihrer Information, std::get_temporary_bufferwird in C ++ 17 veraltet sein.
Deqing
1
@Deqing Ja. Es wird auch in C ++ 20 und aus gutem Grund entfernt (wie unten erwähnt). Bewegen Sie sich also entlang des Betrachters.
Nikos

Antworten:

44

Stroustrup sagt in "The C ++ Programming Language" ( §19.4.4 , SE):

Die Idee ist, dass ein System eine Anzahl von Puffern fester Größe für eine schnelle Zuordnung bereithalten kann, so dass das Anfordern von Speicherplatz für n Objekte Speicherplatz für mehr als n ergeben kann . Es kann jedoch auch weniger ergeben, so dass eine Möglichkeit der Verwendung darin get_temporary_buffer()besteht, optimistisch nach viel zu fragen und dann zu verwenden, was gerade verfügbar ist.
[...] Da get_temporary_buffer()es sich um ein Low-Level-System handelt, das wahrscheinlich für die Verwaltung temporärer Puffer optimiert ist, sollte es nicht als Alternative zu new oder allocator :: allocate () verwendet werden, um längerfristigen Speicher zu erhalten.

Er beginnt auch die Einführung in die beiden Funktionen mit:

Algorithmen benötigen häufig temporären Speicherplatz, um eine akzeptable Leistung zu erzielen.

... scheint aber nirgendwo eine Definition von vorübergehend oder längerfristig zu liefern .

Eine Anekdote in "Von der Mathematik zur generischen Programmierung" erwähnt, dass Stepanov eine falsche Platzhalterimplementierung im ursprünglichen STL-Design bereitgestellt hat:

Zu seiner Überraschung entdeckte er Jahre später, dass alle großen Anbieter, die STL-Implementierungen anbieten, diese schreckliche Implementierung immer noch verwenden [...]

Georg Fritzsche
quelle
12
Es sieht so aus, als wäre die Implementierung von VC ++ einfach eine Schleife, die operator newmit immer kleineren Argumenten aufgerufen wird, bis die Zuordnung erfolgreich ist. Keine besonderen Optimierungen da.
Jalf
9
Gleiches gilt für g ++ 4.5 - scheint gut gemeint zu sein, wird aber von den Anbietern ignoriert.
Georg Fritzsche
4
Klingt so, als hätten sie diese Funktionalität in eine Klasse namenscrazy_allocator
0xbadf00d
Aber bleiben wir ernst - Vielleicht würde man gerne viel Speicherplatz zuweisen. Was wir jetzt tun können, ist das System zu bitten, diese enorme Menge an Speicher zu erhalten - unter Verwendung get_temporary_buffer. Wenn wir jedoch weniger als den angeforderten Betrag erhalten (was sehr schade wäre), versuchen wir weiterhin, unsere Arbeit mit dem Speicher zu erledigen, den wir haben. Könnte besser sein, als bad_allocAusnahmen abzufangen, die durch den Versuch verursacht wurden, mehr Speicher als verfügbar zuzuweisen. Der wahre Nutzen steht und fällt jedoch mit einer guten Implementierung.
0xbadf00d
@jalf Es ist in C ++ 17 veraltet :) (laut cppreference.com ).
4LegsDrivenCat
17

Der Standardbibliothekstyp von Microsoft sagt Folgendes ( hier ):

  • Könnten Sie vielleicht erklären, wann Sie 'get_temporary_buffer' verwenden sollen?

Es hat einen sehr speziellen Zweck. Beachten Sie, dass es keine Ausnahmen wie new (nothrow) auslöst, aber im Gegensatz zu new (nothrow) auch keine Objekte erstellt.

Es wird intern von der STL in Algorithmen wie stabile_Partition () verwendet. Dies geschieht, wenn es magische Wörter wie N3126 25.3.13 [alg.partitions] / 11 gibt: Stable_partition () hat Komplexität "Höchstens (last - first) * log (last - first) Swaps, aber nur lineare Anzahl von Swaps, wenn vorhanden ist genug zusätzlicher Speicher. " Wenn die magischen Wörter "Wenn genügend zusätzlicher Speicher vorhanden ist" angezeigt werden, verwendet die STL get_temporary_buffer (), um zu versuchen, Arbeitsbereich zu erwerben. Wenn dies möglich ist, kann der Algorithmus effizienter implementiert werden. Wenn dies nicht möglich ist, kann der Algorithmus auf eine langsamere Technik zurückgreifen, da das System gefährlich nahe am Speichermangel läuft (oder die beteiligten Bereiche sehr groß sind).

99,9% der STL-Benutzer müssen nie etwas über get_temporary_buffer () wissen.

Jeremy
quelle
9

Der Standard sagt, dass er Speicher für bis zu n Elemente zuweist. Mit anderen Worten, Ihr Beispiel gibt möglicherweise einen Puffer zurück, der groß genug für nur 5 Objekte ist.

Es scheint jedoch ziemlich schwierig, sich einen guten Anwendungsfall dafür vorzustellen. Wenn Sie auf einer Plattform mit sehr wenig Speicher arbeiten, ist dies möglicherweise eine bequeme Möglichkeit, "so viel Speicher wie möglich" zu erhalten.

Aber auf einer so eingeschränkten Plattform würde ich mir vorstellen, dass Sie den Speicherzuweiser so weit wie möglich umgehen und einen Speicherpool oder etwas verwenden, über das Sie die volle Kontrolle haben.

jalf
quelle
4

Zu welchem ​​Zweck sollte ich verwenden std::get_temporary_buffer?

Die Funktion ist in C ++ 17 veraltet, daher lautet die richtige Antwort jetzt "keinen Zweck, verwenden Sie sie nicht".

Raedwald
quelle
2
ptrdiff_t            request = 12
pair<int*,ptrdiff_t> p       = get_temporary_buffer<int>(request);
int*                 base    = p.first;
ptrdiff_t            respond = p.sencond;
assert( is_valid( base, base + respond ) );

Antwort kann weniger als Anfrage sein .

size_t require = 12;
int*   base    = static_cast<int*>( ::operator new( require*sizeof(int) ) );
assert( is_valid( base, base + require ) );

die tatsächliche Größe der Basis muss größer oder gleich erfordern .

OwnWaterloo
quelle
2

Vielleicht (nur eine Vermutung) hat es etwas mit Speicherfragmentierung zu tun. Wenn Sie das zeitliche Gedächtnis weiterhin stark zuweisen und freigeben, aber jedes Mal, wenn Sie dies tun, nach der Zuweisung des temporären Speichers ein beabsichtigtes Langzeitgedächtnis zuweisen, bevor Sie es freigeben, kann dies zu einem fragmentierten Heap führen (denke ich).

Der get_temporary_buffer könnte also als ein Speicherblock gedacht sein, der größer ist als Sie benötigen und der einmal zugewiesen wird (möglicherweise sind viele Blöcke bereit, um mehrere Anforderungen anzunehmen), und jedes Mal, wenn Sie Speicher benötigen, erhalten Sie nur einen der Brocken. Der Speicher wird also nicht fragmentiert.

Daniel Munoz
quelle
1
Sehr interessanter Gedanke. Obwohl es derzeit so implementiert zu sein scheint, dass es bei den meisten Implementierungen einfach funktioniert , könnte dies sehr gut enger unterstützt und in die übrigen Speicherverwaltungsroutinen integriert werden. Ich stimme dafür, dass wir tatsächlich erwarten, dass dies dafür geeignet ist, und Bjarnes Kommentare scheinen es auch anzudeuten.
Gustaf r
Ich habe jetzt nach dem gesucht, was Bjarne dazu sagt, und er sagt, dass es für eine schnelle Zuweisung ohne Initialisierung ausgelegt ist. Es wäre also wie ein Nur-Allokator-Operator (ohne Initialisierer), der neu ist (size_t size), aber das ist schneller zuzuweisen, da er vorab zugewiesen ist.
Daniel Munoz