Dlaczego potrzebuję std :: get_temporary_buffer?


84

W jakim celu powinienem użyć std::get_temporary_buffer? Standard mówi, co następuje:

Uzyskuje wskaźnik do pamięci wystarczającej do przechowywania do n sąsiadujących obiektów T.

Myślałem, że bufor zostanie przydzielony na stosie, ale to nieprawda. Zgodnie ze standardem C ++ ten bufor w rzeczywistości nie jest tymczasowy. Jakie zalety ma ta funkcja w stosunku do funkcji globalnej ::operator new, która również nie konstruuje obiektów. Czy mam rację, że poniższe stwierdzenia są równoważne?

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

Czy ta funkcja istnieje tylko dla cukru składniowego? Dlaczego jest temporaryw jego nazwie?


Jeden przypadek użycia został zasugerowany w Dr Dobb's Journal, 1 lipca 1996, dla implementacji algorytmów:

Jeśli żaden bufor nie może zostać przydzielony lub jeśli jest mniejszy niż żądany, algorytm nadal działa poprawnie, po prostu zwalnia.


2
FYI, std::get_temporary_bufferzostanie wycofane w C ++ 17.
Deqing

1
@Deqing Yes. Zostanie również usunięty w C ++ 20 i nie bez powodu (jak te wymienione poniżej). Więc idź wzdłuż widza ...
Nikos,

Odpowiedzi:


44

Stroustrup mówi w „The C ++ Programming Language” ( §19.4.4 , SE):

Pomysł jest taki, że system może utrzymywać pewną liczbę buforów o stałej wielkości gotowych do szybkiej alokacji, tak że żądanie miejsca dla n obiektów może dać miejsce na więcej niż n . Może jednak przynosić mniej, więc jednym ze sposobów wykorzystania get_temporary_buffer()jest optymistycznie prosić o dużo, a następnie korzystać z tego, co jest dostępne.
[...] Ponieważ get_temporary_buffer()jest niskopoziomowy i prawdopodobnie zoptymalizowany pod kątem zarządzania tymczasowymi buforami, nie powinien być używany jako alternatywa dla new lub alokatora :: przydzielanie () do uzyskiwania pamięci długoterminowej.

Rozpoczyna również wprowadzenie do obu funkcji od:

Algorytmy często wymagają tymczasowej przestrzeni, aby działać w akceptowalny sposób.

... ale wydaje się, że nigdzie nie ma definicji tymczasowego ani długoterminowego .

Anegdota w „Z matematyki Generic Programming” wspomina, że Stepanov dostarczył fałszywe realizację zastępczy w pierwotnym projekcie STL, jednakże:

Ku swojemu zdziwieniu, po latach odkrył, że wszyscy główni dostawcy dostarczający implementacje STL nadal używają tej strasznej implementacji [...]


12
Wygląda na to, że implementacja tego w VC ++ jest po prostu wywołaniem pętli operator newz coraz mniejszymi argumentami, aż do pomyślnego przydzielenia. Nie ma tam żadnych specjalnych optymalizacji.
jalf

9
To samo z g ++ 4.5 - wydaje się, że było to dobrze zaplanowane, ale zignorowane przez dostawców.
Georg Fritzsche,

4
Wygląda na to, że powinni byli crazy_allocator
opakować

Ale bądźmy poważni - może ktoś byłby szczęśliwy, gdyby przydzielił dużo miejsca. To, co możemy teraz zrobić, to poprosić system o uzyskanie tak dużej ilości pamięci - używając get_temporary_buffer. Jeśli jednak otrzymamy kwotę mniejszą niż żądana (a szkoda), staramy się nadal pracować z magazynem, który mamy. Może być lepsze niż przechwytywanie bad_allocwyjątków spowodowanych próbą przydzielenia większej ilości pamięci niż jest dostępna. Jednak prawdziwa użyteczność pozostaje i spada przy dobrej implementacji.
0xbadf00d

@jalf To przestarzałe w C ++ 17 :) (według cppreference.com ).
4LegsDrivenCat

17

Bibliotekarz Microsoftu mówi co następuje ( tutaj ):

  • Czy mógłbyś wyjaśnić, kiedy używać „get_temporary_buffer”

Ma bardzo wyspecjalizowany cel. Zauważ, że nie zgłasza wyjątków, takich jak new (nothrow), ale także nie konstruuje obiektów, w przeciwieństwie do new (nothrow).

Jest używany wewnętrznie przez STL w algorytmach, takich jak stabilna_partycja (). Dzieje się tak, gdy istnieją magiczne słowa, takie jak N3126 25.3.13 [alg.partitions] / 11: stabilna_partycja () ma złożoność "Co najwyżej (ostatnia - pierwsza) * log (ostatnia - pierwsza) zamiany, ale tylko liniowa liczba zamian, jeśli są wystarczy dodatkowej pamięci ”. Kiedy pojawiają się magiczne słowa „jeśli jest wystarczająco dużo dodatkowej pamięci”, STL używa metody get_temporary_buffer () do próby uzyskania przestrzeni roboczej. Jeśli tak, to może wydajniej implementować algorytm. Jeśli nie może, ponieważ system działa niebezpiecznie blisko braku pamięci (lub zakresy, których to dotyczy, są ogromne), algorytm może wrócić do wolniejszej techniki.

99,9% użytkowników STL nigdy nie będzie musiało wiedzieć o get_temporary_buffer ().


9

Norma mówi, że przydziela miejsce na maksymalnie n elementy. Innymi słowy, twój przykład może zwrócić bufor wystarczająco duży dla tylko 5 obiektów.

Wydaje się jednak, że trudno sobie wyobrazić dobry przypadek użycia tego. Być może, jeśli pracujesz na platformie z bardzo ograniczoną pamięcią, jest to wygodny sposób na uzyskanie „jak największej ilości pamięci”.

Ale na takiej ograniczonej platformie wyobrażam sobie, że ominiesz alokator pamięci tak bardzo, jak to możliwe, i użyjesz puli pamięci lub czegoś, nad czym masz pełną kontrolę.


4

W jakim celu powinienem użyć std::get_temporary_buffer?

Funkcja jest przestarzała w C ++ 17, więc poprawna odpowiedź brzmi teraz „bez celu, nie używaj jej”.


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 ) );

odpowiedź może być mniejsza niż prośba .

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

rzeczywisty rozmiar podstawy musi być większy lub równy wymaganiom .


2

Być może (tylko przypuszczenie) ma to coś wspólnego z fragmentacją pamięci. Jeśli nadal będziesz w dużym stopniu alokować i zwalniać pamięć czasową, ale za każdym razem, gdy to robisz, przydzielasz jakąś długoterminową zamierzoną pamięć po przydzieleniu temp, ale przed jej zwolnieniem, możesz skończyć z pofragmentowaną stertą (tak sądzę).

Więc get_temporary_buffer może być zaprojektowany jako większy niż byś potrzebował fragment pamięci, który jest przydzielany raz (być może jest wiele fragmentów gotowych do zaakceptowania wielu żądań), a za każdym razem, gdy potrzebujesz pamięci, po prostu dostajesz jedną z kawałki. Więc pamięć nie ulega fragmentacji.


1
Bardzo ciekawa myśl. Chociaż wydaje się, że jest obecnie implementowany jako `` po prostu-zrób-coś-co-działa '' ' w większości implementacji, może to być ściślej wspierane i zintegrowane z pozostałymi procedurami zarządzania pamięcią. Głosuję na nas, spodziewając się, że to będzie do tego odpowiednie, a komentarze Bjarnesa wydają się też do tego wskazywać.
gustaf r

Szukałem teraz tego, co mówi o tym Bjarne i mówi, że jest on zaprojektowany do szybkiej alokacji bez inicjalizacji. Byłoby to więc jak operator alokatora (nie inicjujący) void * new (rozmiar_t), ale jest szybszy do przydzielenia, ponieważ jest wstępnie przydzielony.
Daniel Munoz
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.