Altri hanno già affrontato gli altri problemi, quindi guarderò solo a un punto: vuoi mai eliminare manualmente un oggetto.
La risposta è si. @DavidSchwartz ha fornito un esempio, ma è abbastanza insolito. Darò un esempio che è sotto il cofano di ciò che molti programmatori C ++ usano tutto il tempo: std::vector
(e std::deque
, sebbene non sia usato così tanto).
Come la maggior parte delle persone sa, std::vector
assegnerà un blocco di memoria più grande quando / se si aggiungono più elementi di quelli che la sua allocazione corrente può contenere. Quando lo fa, tuttavia, ha un blocco di memoria che è in grado di contenere più oggetti di quanti ne siano attualmente nel vettore.
Per gestirlo, ciò che vector
fa sotto le coperte è allocare memoria grezza tramite l' Allocator
oggetto (che, a meno che non specifichi diversamente, significa che utilizza ::operator new
). Quindi, quando si utilizza (ad esempio) push_back
per aggiungere un elemento a vector
, internamente il vettore utilizza a placement new
per creare un elemento nella parte (precedentemente) inutilizzata del suo spazio di memoria.
Ora, cosa succede quando / se erase
un elemento dal vettore? Non può semplicemente usare delete
- questo libererebbe il suo intero blocco di memoria; deve distruggere un oggetto in quella memoria senza distruggerne gli altri, o liberare nessuno dei blocchi di memoria che controlla (ad esempio, se si erase
5 elementi da un vettore, quindi immediatamente push_back
altri 5 elementi, è garantito che il vettore non si riallocherà memoria quando lo fai.
Per fare ciò, il vettore distrugge direttamente gli oggetti nella memoria chiamando esplicitamente il distruttore, non usando delete
.
Se, forse, qualcun altro dovesse scrivere un contenitore usando l'archiviazione contigua all'incirca come vector
fa un (o una sua variante, come std::deque
fa in realtà), quasi certamente vorrai usare la stessa tecnica.
Solo per esempio, consideriamo come potresti scrivere codice per un ring-buffer circolare.
#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 differenza dei contenitori standard, questo utilizza operator new
e operator delete
direttamente. Per un uso reale, probabilmente vorrai usare una classe allocatore, ma per il momento farebbe più distrarre che contribuire (IMO, comunque).