Inni już zajęli się innymi problemami, więc przyjrzę się tylko jednej kwestii: czy kiedykolwiek chcesz ręcznie usunąć obiekt.
Odpowiedź brzmi tak. @DavidSchwartz podał jeden przykład, ale jest on dość nietypowy. Podam przykład, który jest pod maską tego, z czego wielu programistów C ++ korzysta cały czas: std::vector
(i std::deque
chociaż nie jest używany tak często).
Jak większość ludzi wie, std::vector
przydzieli większy blok pamięci, gdy / jeśli dodasz więcej elementów, niż może pomieścić obecna alokacja. Jednak gdy to robi, ma blok pamięci, który może pomieścić więcej obiektów niż obecnie znajduje się w wektorze.
Aby sobie z tym poradzić, to, co vector
robi pod osłonami, polega na przydzielaniu pamięci surowej za pośrednictwem Allocator
obiektu (który, o ile nie określono inaczej, oznacza, że używa ::operator new
). Następnie, gdy użyjesz (na przykład), push_back
aby dodać element do vector
, wewnętrznie wektor używa a, placement new
aby utworzyć element w (wcześniej) nieużywanej części swojej przestrzeni pamięci.
Co się dzieje, gdy / jeśli jesteś erase
elementem z wektora? Nie może po prostu używać delete
- to zwolniłoby cały blok pamięci; musi zniszczyć jeden obiekt w tej pamięci bez niszczenia innych lub zwolnienia żadnego z bloków pamięci, które kontroluje (na przykład, jeśli masz erase
5 elementów z wektora, a następnie natychmiast push_back
5 więcej elementów, to gwarantuje, że wektor nie zostanie ponownie przydzielony pamięć, kiedy to robisz.
Aby to zrobić, wektor bezpośrednio niszczy obiekty w pamięci, jawnie wywołując destruktor, a nie używając delete
.
Jeśli, być może, ktoś inny napisałby kontener przy użyciu ciągłej pamięci, mniej więcej tak, jak vector
robi (lub jakiś jej wariant, jak std::deque
naprawdę), prawie na pewno chciałbyś użyć tej samej techniki.
Na przykład zastanówmy się, jak możesz napisać kod dla okrągłego bufora pierścieniowego.
#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
W przeciwieństwie do standardowych pojemników, to używa operator new
i operator delete
bezpośrednio. W przypadku rzeczywistego użytku prawdopodobnie zechcesz użyć klasy alokatora, ale w tej chwili bardziej rozpraszałoby to niż wnosiło wkład (w każdym razie IMO).