Otros ya han abordado los otros problemas, por lo que solo miraré un punto: ¿alguna vez desea eliminar manualmente un objeto?
La respuesta es sí. @DavidSchwartz dio un ejemplo, pero es bastante inusual. Daré un ejemplo que está bajo el capó de lo que muchos programadores de C ++ usan todo el tiempo: std::vector
(y std::deque
, aunque no se usa tanto).
Como la mayoría de la gente sabe, std::vector
asignará un bloque de memoria más grande cuando / si agrega más elementos de los que puede contener su asignación actual. Sin embargo, cuando hace esto, tiene un bloque de memoria que es capaz de contener más objetos de los que hay actualmente en el vector.
Para administrar eso, lo que vector
hace bajo las sábanas es asignar memoria sin procesar a través del Allocator
objeto (que, a menos que especifique lo contrario, significa que usa ::operator new
). Luego, cuando usa (por ejemplo) push_back
para agregar un elemento al vector
, internamente el vector usa a placement new
para crear un elemento en la parte (previamente) no utilizada de su espacio de memoria.
Ahora, ¿qué sucede cuando / si tienes erase
un elemento del vector? No puede simplemente usar delete
, eso liberaría todo su bloque de memoria; necesita destruir un objeto en esa memoria sin destruir ningún otro, o liberar ninguno de los bloques de memoria que controla (por ejemplo, si usted erase
5 elementos de un vector, luego inmediatamente push_back
5 elementos más, se garantiza que el vector no se reasignará memoria cuando lo hagas.
Para hacer eso, el vector destruye directamente los objetos en la memoria llamando explícitamente al destructor, no usando delete
.
Si, por casualidad, alguien más escribiera un contenedor usando almacenamiento contiguo más o menos como lo vector
hace (o alguna variante de eso, como std::deque
realmente lo hace), es casi seguro que querrá usar la misma técnica.
Por ejemplo, consideremos cómo podría escribir código para un búfer circular circular.
#ifndef CBUFFER_H_INC
#define CBUFFER_H_INC
template <class T>
class circular_buffer {
T *data;
unsigned read_pos;
unsigned write_pos;
unsigned in_use;
const unsigned capacity;
public:
circular_buffer(unsigned size) :
data((T *)operator new(size * sizeof(T))),
read_pos(0),
write_pos(0),
in_use(0),
capacity(size)
{}
void push(T const &t) {
// ensure there's room in buffer:
if (in_use == capacity)
pop();
// construct copy of object in-place into buffer
new(&data[write_pos++]) T(t);
// keep pointer in bounds.
write_pos %= capacity;
++in_use;
}
// return oldest object in queue:
T front() {
return data[read_pos];
}
// remove oldest object from queue:
void pop() {
// destroy the object:
data[read_pos++].~T();
// keep pointer in bounds.
read_pos %= capacity;
--in_use;
}
~circular_buffer() {
// first destroy any content
while (in_use != 0)
pop();
// then release the buffer.
operator delete(data);
}
};
#endif
A diferencia de los contenedores estándar, este usa operator new
y operator delete
directamente. Para uso real, probablemente quieras usar una clase de asignador, pero por el momento haría más para distraer que para contribuir (en mi opinión, de todos modos).