¿Por qué necesito std :: get_temporary_buffer?

84

¿Con qué propósito debo usar std::get_temporary_buffer? Standard dice lo siguiente:

Obtiene un puntero de almacenamiento suficiente para almacenar hasta n objetos T adyacentes.

Pensé que el búfer se asignará a la pila, pero eso no es cierto. Según el estándar C ++, este búfer en realidad no es temporal. ¿Qué ventajas tiene esta función sobre la función global ::operator new, que tampoco construye los objetos? ¿Tengo razón en que las siguientes declaraciones son equivalentes?

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

¿Esta función solo existe para syntax sugar? ¿Por qué hay temporaryen su nombre?


En el Dr. Dobb's Journal, 1 de julio de 1996, se sugirió un caso de uso para implementar algoritmos:

Si no se puede asignar ningún búfer, o si es más pequeño de lo solicitado, el algoritmo aún funciona correctamente, simplemente se ralentiza.

Kirill V. Lyadvinsky
fuente
2
Para su información, std::get_temporary_bufferquedará obsoleto en C ++ 17.
Deqing
1
@Deqing Sí. También se eliminará en C ++ 20 y por una buena razón (como las que se mencionan a continuación). Así moverse a lo largo espectador ..
Nikos

Respuestas:

44

Stroustrup dice en "El lenguaje de programación C ++" ( §19.4.4 , SE):

La idea es que un sistema puede mantener una cantidad de búferes de tamaño fijo listos para una asignación rápida, de modo que la solicitud de espacio para n objetos pueda generar espacio para más de n . Sin embargo, también puede producir menos, por lo que una forma de usarlo get_temporary_buffer()es pedir mucho de manera optimista y luego usar lo que esté disponible.
[...] Debido a que get_temporary_buffer()es de bajo nivel y es probable que esté optimizado para administrar búferes temporales, no debe usarse como una alternativa a new o allocator :: allocate () para obtener almacenamiento a más largo plazo.

También comienza la introducción a las dos funciones con:

Los algoritmos a menudo requieren un espacio temporal para funcionar de manera aceptable.

... pero no parece proporcionar una definición de temporal o de largo plazo en ninguna parte.

Sin embargo, una anécdota en "De las matemáticas a la programación genérica" menciona que Stepanov proporcionó una implementación de marcador de posición falsa en el diseño STL original:

Para su sorpresa, descubrió años más tarde que todos los principales proveedores que proporcionan implementaciones STL todavía utilizan esta terrible implementación [...]

Georg Fritzsche
fuente
12
Parece que la implementación de VC ++ de esto es simplemente una llamada de bucle operator newcon argumentos sucesivamente más pequeños hasta que la asignación se realiza correctamente. No hay optimizaciones especiales allí.
jalf
9
Lo mismo con g ++ 4.5: parece que fue bien intencionado, pero los proveedores lo ignoraron.
Georg Fritzsche
4
Parece que deberían haber envuelto esta funcionalidad en una clase llamadacrazy_allocator
0xbadf00d
Pero seamos serios: tal vez uno estaría feliz de asignar una gran cantidad de almacenamiento. Lo que podemos hacer ahora es pedirle al sistema que obtenga esa enorme cantidad de almacenamiento: utilizando get_temporary_buffer. Sin embargo, si recibimos menos de la cantidad solicitada (lo que sería una verdadera lástima) seguimos intentando hacer nuestro trabajo con el almacenamiento que tenemos. Podría ser mejor que detectar bad_allocexcepciones provocadas por el intento de asignar más memoria de la disponible. Sin embargo, la utilidad real se mantiene y cae con una buena implementación.
0xbadf00d
@jalf Está obsoleto en C ++ 17 :) (según cppreference.com ).
4LegsDrivenCat
17

El tipo de biblioteca estándar de Microsoft dice lo siguiente ( aquí ):

  • ¿Podría tal vez explicar cuándo usar 'get_temporary_buffer'

Tiene un propósito muy especializado. Tenga en cuenta que no lanza excepciones, como new (nothrow), pero tampoco construye objetos, a diferencia de new (nothrow).

El STL lo usa internamente en algoritmos como stable_partition (). Esto sucede cuando hay palabras mágicas como N3126 25.3.13 [alg.partitions] / 11: stable_partition () tiene complejidad "Como máximo (último - primero) * log (último - primero) intercambios, pero solo un número lineal de intercambios si hay es suficiente memoria extra ". Cuando aparecen las palabras mágicas "si hay suficiente memoria extra", el STL usa get_temporary_buffer () para intentar adquirir espacio de trabajo. Si puede, entonces puede implementar el algoritmo de manera más eficiente. Si no puede, porque el sistema se está ejecutando peligrosamente cerca de la memoria agotada (o los rangos involucrados son enormes), el algoritmo puede recurrir a una técnica más lenta.

El 99,9% de los usuarios de STL nunca necesitarán saber acerca de get_temporary_buffer ().

Jeremy
fuente
9

El estándar dice que asigna almacenamiento para hasta n elementos. En otras palabras, su ejemplo podría devolver un búfer lo suficientemente grande para solo 5 objetos.

Sin embargo, parece bastante difícil imaginar un buen caso de uso para esto. Quizás si está trabajando en una plataforma con mucha memoria limitada, es una forma conveniente de obtener "tanta memoria como sea posible".

Pero en una plataforma tan limitada, me imagino que pasaría por alto el asignador de memoria tanto como sea posible y usaría un grupo de memoria o algo sobre lo que tenga control total.

jalf
fuente
4

Con que proposito debo usar std::get_temporary_buffer?

La función está obsoleta en C ++ 17, por lo que la respuesta correcta ahora es "sin ningún propósito, no la use".

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

responder puede ser menor que la solicitud .

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

el tamaño real de la base debe ser mayor o igual para requerir .

OwnWaterloo
fuente
2

Quizás (solo una suposición) tenga algo que ver con la fragmentación de la memoria. Si sigue asignando y desasignando mucho la memoria temporal, pero cada vez que lo hace, asigna algo de memoria prevista a largo plazo después de asignar la temperatura pero antes de desasignarla, puede terminar con un montón fragmentado (supongo).

Por lo tanto, el get_temporary_buffer podría estar destinado a ser un trozo de memoria más grande de lo que necesitaría que se asigna una vez (tal vez hay muchos trozos listos para aceptar múltiples solicitudes), y cada vez que necesita memoria, solo obtiene uno de los trozos. Para que la memoria no se fragmente.

Daniel Muñoz
fuente
1
Pensamiento muy interesante. Aunque parece estar implementado actualmente como un vamos a hacer algo que funcione en la mayoría de las implementaciones, esto podría muy bien estar respaldado e integrado de manera más estricta con el resto de las rutinas de administración de memoria. Voto por nosotros esperando que esto sea adecuado para él, y los comentarios de Bjarnes parecen insinuarlo también.
gustaf r
He buscado ahora lo que dice Bjarne al respecto y dice que está diseñado para una asignación rápida sin inicialización. Por lo tanto, sería como un operador void * de solo asignador (no inicializador) nuevo (tamaño_t tamaño), pero que es más rápido de asignar, ya que está preasignado.
Daniel Munoz