c++ - new - ¿Qué usos hay para la "nueva colocación"?
dynamic memory c++ new (22)
La nueva ubicación le permite construir un objeto en la memoria que ya está asignada.
Es posible que desee hacer esto para las optimizaciones (es más rápido no reasignar todo el tiempo) pero necesita reconstruir un objeto varias veces. Si necesita seguir reasignándolo, puede ser más eficiente asignar más de lo que necesita, aunque todavía no quiera usarlo.
Devex da un buen ejemplo :
C ++ estándar también admite la colocación de un nuevo operador, que construye un objeto en un búfer asignado previamente. Esto es útil cuando se crea un grupo de memoria, un recolector de basura o simplemente cuando el rendimiento y la seguridad de excepción son primordiales (no hay peligro de falla de asignación ya que la memoria ya ha sido asignada, y construir un objeto en un búfer preasignado lleva menos tiempo) :
char *buf = new char[sizeof(string)]; // pre-allocated buffer
string *p = new (buf) string("hi"); // placement new
string *q = new string("hi"); // ordinary heap allocation
También es posible que desee asegurarse de que no haya un error de asignación en una parte determinada del código crítico (por ejemplo, tal vez trabaje en un marcapasos). En ese caso usted querría utilizar la nueva ubicación.
Desasignación en colocación nueva.
No debe desasignar todos los objetos que utilizan el búfer de memoria. En su lugar, debe eliminar [] solo el búfer original. Tendrías que llamar a los destructores directamente de tus clases manualmente. Para una buena sugerencia sobre esto, consulte las preguntas frecuentes de Stroustrup en: ¿Hay una "eliminación de ubicación" ?
¿Alguien aquí alguna vez ha usado la "colocación nueva" de C ++? Si es así, ¿para qué? Me parece que solo sería útil en hardware mapeado en memoria.
Aquí está el uso del asesino para el constructor in situ de C ++: alinearse con una línea de caché, así como con otras potencias de 2 límites. Aquí está mi algoritmo de alineación de puntero ultrarrápido a cualquier potencia de 2 límites con 5 o menos instrucciones de un solo ciclo :
/* Quickly aligns the given pointer to a power of two boundary IN BYTES.
@return An aligned pointer of typename T.
@brief Algorithm is a 2''s compliment trick that works by masking off
the desired number in 2''s compliment and adding them to the
pointer.
@param pointer The pointer to align.
@param boundary_byte_count The boundary byte count that must be an even
power of 2.
@warning Function does not check if the boundary is a power of 2! */
template <typename T = char>
inline T* AlignUp(void* pointer, uintptr_t boundary_byte_count) {
uintptr_t value = reinterpret_cast<uintptr_t>(pointer);
value += (((~value) + 1) & (boundary_byte_count - 1));
return reinterpret_cast<T*>(value);
}
struct Foo { Foo () {} };
char buffer[sizeof (Foo) + 64];
Foo* foo = new (AlignUp<Foo> (buffer, 64)) Foo ();
Ahora, ¿no es eso solo poner una sonrisa en tu cara (:-). Yo ♥♥♥ C ++ 1x
Creo que esto no se ha resaltado con ninguna respuesta, pero otro buen ejemplo y uso para la nueva ubicación es reducir la fragmentación de la memoria (mediante el uso de grupos de memoria). Esto es especialmente útil en sistemas integrados y de alta disponibilidad. En este último caso es especialmente importante porque para un sistema que tiene que funcionar 24/365 días es muy importante no tener fragmentación. Este problema no tiene nada que ver con la pérdida de memoria.
Incluso cuando se utiliza una implementación malloc muy buena (o una función de gestión de memoria similar) es muy difícil lidiar con la fragmentación durante mucho tiempo. En algún momento, si no maneja inteligentemente las llamadas de reserva / liberación de memoria, podría terminar con muchos huecos pequeños que son difíciles de reutilizar (asignar a nuevas reservas). Por lo tanto, una de las soluciones que se utilizan en este caso es usar un grupo de memoria para asignar de antemano la memoria para los objetos de la aplicación. Cada vez que necesite memoria para algún objeto, solo utilizará la nueva ubicación para crear un nuevo objeto en la memoria ya reservada.
De esta manera, una vez que se inicie la aplicación, ya tendrá reservada toda la memoria necesaria. Toda la nueva reserva / liberación de memoria va a los grupos asignados (puede tener varios grupos, uno para cada clase de objeto diferente). En este caso, no se produce una fragmentación de la memoria, ya que no habrá huecos y su sistema puede funcionar durante largos períodos (años) sin sufrir fragmentación.
Vi esto en la práctica especialmente para el VxWorks RTOS ya que su sistema de asignación de memoria predeterminado sufre mucho de la fragmentación. Por lo tanto, la asignación de memoria a través del método new / malloc estándar estaba básicamente prohibida en el proyecto. Todas las reservas de memoria deben ir a un grupo de memoria dedicado.
El único lugar en el que me he encontrado es en contenedores que asignan un búfer contiguo y luego lo llenan con objetos según sea necesario. Como se mencionó, std :: vector podría hacer esto, y sé que algunas versiones de MFC CArray y / o CList hicieron esto (porque ahí es donde lo encontré por primera vez). El método de asignación excesiva de búfer es una optimización muy útil, y la nueva ubicación es prácticamente la única forma de construir objetos en ese escenario. También se usa a veces para construir objetos en bloques de memoria asignados fuera de su código directo.
Lo he usado en una capacidad similar, aunque no aparece a menudo. Sin embargo, es una herramienta útil para la caja de herramientas de C ++.
En general, la nueva ubicación se utiliza para deshacerse del costo de asignación de un "nuevo normal".
Otro escenario donde lo usé es un lugar donde quería tener acceso al puntero a un objeto que aún estaba por construirse, para implementar un singleton por documento.
En realidad, es necesario implementar cualquier tipo de estructura de datos que asigne más memoria que la mínima requerida para el número de elementos insertados (es decir, cualquier otra cosa que no sea una estructura vinculada que asigna un nodo a la vez).
Tome contenedores como unordered_map
, vector
o deque
. Todos estos asignan más memoria de la que se requiere mínimamente para los elementos que ha insertado hasta ahora para evitar requerir una asignación de montón para cada inserción individual. Usemos vector
como el ejemplo más simple.
Cuando tu lo hagas:
vector<Foo> vec;
// Allocate memory for a thousand Foos:
vec.reserve(1000);
... que en realidad no construye mil foos. Simplemente asigna / reserva memoria para ellos. Si vector
no usara la ubicación nueva aquí, sería la construcción por defecto de Foos
todo el lugar, además de tener que invocar a sus destructores, incluso para los elementos que nunca insertó en primer lugar.
Asignación! = Construcción, Liberación! = Destrucción
En términos generales, para implementar muchas estructuras de datos como la anterior, no puede tratar la asignación de memoria y la construcción de elementos como una cosa indivisible, y tampoco puede tratar la liberación de memoria y la destrucción de elementos como una cosa indivisible.
Tiene que haber una separación entre estas ideas para evitar invocar de forma innecesaria constructores y destructores innecesariamente a la izquierda y a la derecha, y es por eso que la biblioteca estándar separa la idea de std::allocator
(que no construye ni destruye elementos cuando asigna / libera memoria) *) lejos de los contenedores que lo utilizan, que construyen manualmente los elementos utilizando la ubicación nueva y destruyen manualmente los elementos mediante invocaciones explícitas de los destructores.
- Odio el diseño de
std::allocator
pero es un tema diferente que evitaré. :-RE
De todos modos, tiendo a usarlo mucho ya que he escrito varios contenedores de C ++ de propósito general que cumplen con los estándares y que no se pudieron construir en términos de los existentes. Entre ellos se incluye una implementación de vector pequeño que construí hace un par de décadas para evitar las asignaciones de pila en casos comunes, y un trie eficiente en memoria (no asigna un nodo a la vez). En ambos casos, realmente no pude implementarlos utilizando los contenedores existentes, por lo que tuve que usar la placement new
para evitar invocar de forma superflua a los constructores y destructores sobre cosas innecesarias de izquierda y derecha.
Naturalmente, si alguna vez trabaja con asignadores personalizados para asignar objetos individualmente, como una lista gratuita, entonces generalmente también querrá usar una placement new
, como esta (ejemplo básico que no se preocupa por la excepción de seguridad o RAII):
Foo* foo = new(free_list.allocate()) Foo(...);
...
foo->~Foo();
free_list.free(foo);
Es útil si desea separar la asignación de la inicialización. STL utiliza la ubicación nueva para crear elementos de contenedor.
Es útil si está creando un kernel. ¿Dónde coloca el código del kernel que leyó en el disco o en la tabla de páginas? Necesitas saber dónde saltar.
O en otras circunstancias muy raras, como cuando tienes un montón de espacio asignado y quieres colocar algunas estructuras una detrás de la otra. Se pueden empaquetar de esta manera sin la necesidad del operador offsetof (). Sin embargo, hay otros trucos para eso también.
También creo que algunas implementaciones de STL hacen uso de la ubicación nueva, como std :: vector. Asignan espacio para 2 ^ n elementos de esa manera y no necesitan reasignar siempre.
Es utilizado por std::vector<>
porque std::vector<>
normalmente asigna más memoria que objects
en el vector<>
.
He visto que se usa como un pequeño hack de rendimiento para un puntero de "tipo dinámico" (en la sección "Bajo el capó"):
Pero aquí está el truco complicado que solía obtener rápido rendimiento para tipos pequeños: si el valor que se mantiene puede caber dentro de un vacío *, en realidad no me molesto en asignar un nuevo objeto, lo fuerzo en el puntero utilizando la ubicación nueva .
Head Geek: ¡BINGO! Lo tienes totalmente, para eso es perfecto. En muchos entornos integrados, las restricciones externas y / o el escenario de uso general obliga al programador a separar la asignación de un objeto de su inicialización. Reunidos, C ++ llama a esto "instanciación"; pero siempre que la acción del constructor se debe invocar explícitamente SIN asignación dinámica o automática, la ubicación nueva es la forma de hacerlo. También es la forma perfecta de ubicar un objeto C ++ global que está anclado a la dirección de un componente de hardware (E / S asignada en memoria), o para cualquier objeto estático que, por cualquier motivo, debe residir en una dirección fija.
La nueva ubicación también es muy útil cuando se serializa (por ejemplo, con boost :: serialización). En 10 años de c ++, este es solo el segundo caso en el que he necesitado una nueva ubicación (tercero si incluye entrevistas :)).
Lo he usado en programación en tiempo real. Normalmente , no queremos realizar ninguna asignación dinámica (o desasignación) después de que se inicie el sistema, porque no hay garantía de cuánto tiempo tomará.
Lo que puedo hacer es preasignar una gran cantidad de memoria (lo suficientemente grande como para contener cualquier cantidad de lo que la clase pueda requerir). Luego, una vez que descubro en tiempo de ejecución cómo construir las cosas, la ubicación nueva se puede usar para construir objetos justo donde los quiero. Una situación en la que sé que lo usé fue para ayudar a crear un búfer circular heterogéneo.
Ciertamente no es para los débiles de corazón, pero es por eso que hacen que la sintaxis sea un poco retorcida.
Lo he usado para almacenar objetos con archivos asignados en memoria.
El ejemplo específico fue una base de datos de imágenes que procesó grandes cantidades de imágenes grandes (más de lo que cabía en la memoria).
Lo he usado para construir objetos asignados en la pila a través de alloca ().
enchufe descarado: blogueé sobre esto here .
Lo he usado para crear objetos basados en la memoria que contiene mensajes recibidos de la red.
Lo he usado para crear una clase Variant (es decir, un objeto que puede representar un valor único que puede ser uno de varios tipos diferentes).
Si todos los tipos de valor admitidos por la clase Variant son tipos de POD (por ejemplo, int, float, double, bool), entonces una unión de estilo C etiquetada es suficiente, pero si desea que algunos de los tipos de valor sean objetos C ++ ( por ejemplo, std :: string), la función de unión C no funcionará, ya que los tipos de datos que no son POD no pueden declararse como parte de una unión.
Entonces, en lugar de eso, asigno una matriz de bytes que es lo suficientemente grande (por ejemplo, sizeof (the_largest_data_type_I_support)) y uso la ubicación nueva para inicializar el objeto C ++ apropiado en esa área cuando la Variante está configurada para mantener un valor de ese tipo. (Y eliminar la ubicación de antemano al cambiar de un tipo de datos distinto de POD, por supuesto)
Lo usamos con pools de memoria personalizados. Solo un boceto:
class Pool {
public:
Pool() { /* implementation details irrelevant */ };
virtual ~Pool() { /* ditto */ };
virtual void *allocate(size_t);
virtual void deallocate(void *);
static Pool::misc_pool() { return misc_pool_p; /* global MiscPool for general use */ }
};
class ClusterPool : public Pool { /* ... */ };
class FastPool : public Pool { /* ... */ };
class MapPool : public Pool { /* ... */ };
class MiscPool : public Pool { /* ... */ };
// elsewhere...
void *pnew_new(size_t size)
{
return Pool::misc_pool()->allocate(size);
}
void *pnew_new(size_t size, Pool *pool_p)
{
if (!pool_p) {
return Pool::misc_pool()->allocate(size);
}
else {
return pool_p->allocate(size);
}
}
void pnew_delete(void *p)
{
Pool *hp = Pool::find_pool(p);
// note: if p == 0, then Pool::find_pool(p) will return 0.
if (hp) {
hp->deallocate(p);
}
}
// elsewhere...
class Obj {
public:
// misc ctors, dtors, etc.
// just a sampling of new/del operators
void *operator new(size_t s) { return pnew_new(s); }
void *operator new(size_t s, Pool *hp) { return pnew_new(s, hp); }
void operator delete(void *dp) { pnew_delete(dp); }
void operator delete(void *dp, Pool*) { pnew_delete(dp); }
void *operator new[](size_t s) { return pnew_new(s); }
void *operator new[](size_t s, Pool* hp) { return pnew_new(s, hp); }
void operator delete[](void *dp) { pnew_delete(dp); }
void operator delete[](void *dp, Pool*) { pnew_delete(dp); }
};
// elsewhere...
ClusterPool *cp = new ClusterPool(arg1, arg2, ...);
Obj *new_obj = new (cp) Obj(arg_a, arg_b, ...);
Ahora puede agrupar objetos en un solo espacio de memoria, seleccionar un asignador que sea muy rápido pero que no realice desasignaciones, usar mapeo de memoria y cualquier otra semántica que desee imponer al elegir el conjunto y pasarlo como un argumento a la ubicación de un objeto. nuevo operador
Los motores de secuencias de comandos pueden usarlo en la interfaz nativa para asignar objetos nativos de secuencias de comandos. Consulte Angelscript (www.angelcode.com/angelscript) para ver ejemplos.
Puede ser útil cuando se usa memoria compartida, entre otros usos ... Por ejemplo: http://www.boost.org/doc/libs/1_51_0/doc/html/interprocess/synchronization_mechanisms.html#interprocess.synchronization_mechanisms.conditions.conditions_anonymous_example
También es útil cuando desea reinicializar estructuras globales o asignadas estáticamente.
La forma antigua de C usaba memset()
para establecer todos los elementos en 0. No se puede hacer eso en C ++ debido a vtables y constructores de objetos personalizados.
Así que a veces uso lo siguiente
static Mystruct m;
for(...) {
// re-initialize the structure. Note the use of placement new
// and the extra parenthesis after Mystruct to force initialization.
new (&m) Mystruct();
// do-some work that modifies m''s content.
}
Vea el archivo fp.h en el proyecto xll en http://xll.codeplex.com. Resuelve el problema de la "falta de armonía con el compilador" para los arreglos que les gusta llevar sus dimensiones con ellos.
typedef struct _FP { unsigned short int rows; unsigned short int columns; double array[1]; /* Actually, array[rows][columns] */ } FP;