Я хотел бы воспользоваться возможностью, чтобы заинтересовать вас интересной, но несколько сложной темой: исключения.
- Если вы начнете выделять память самостоятельно и впоследствии будете играть с необработанными указателями, вы окажетесь в трудном положении, чтобы избежать утечек памяти.
- Даже если вы доверяете учет памяти нужному классу (скажем,
std::unique_ptr<char[]>
), вы все равно должны убедиться, что операции, которые изменяют объект, оставляют его в согласованном состоянии в случае их сбоя.
Например, вот простой класс с неправильным resize
методом (который лежит в основе большинства кода):
template <typename T>
class DynamicArray {
public:
// Constructor
DynamicArray(): size(0), capacity(0), buffer(0) {}
// Destructor
~DynamicArray() {
if (buffer == 0) { return; }
for(size_t i = 0; i != size; ++i) {
T* t = buffer + i;
t->~T();
}
free(buffer); // using delete[] would require all objects to be built
}
private:
size_t size;
size_t capacity;
T* buffer;
};
Хорошо, так что это легкая часть (хотя уже немного хитрая).
Теперь, как вы толкаете новый элемент в конце?
template <typename T>
void DynamicArray<T>::resize(size_t n) {
// The *easy* case
if (n <= size) {
for (; n < size; ++n) {
(buffer + n)->~T();
}
size = n;
return;
}
// The *hard* case
// new size
size_t const oldsize = size;
size = n;
// new capacity
if (capacity == 0) { capacity = 1; }
while (capacity < n) { capacity *= 2; }
// new buffer (copied)
try {
T* newbuffer = (T*)malloc(capacity*sizeof(T));
// copy
for (size_t i = 0; i != oldsize; ++i) {
new (newbuffer + i) T(*(buffer + i));
}
free(buffer)
buffer = newbuffer;
} catch(...) {
free(newbuffer);
throw;
}
}
Чувствуется, нет?
Я имею в виду, мы даже позаботимся о возможном исключении, сгенерированном конструктором копирования T
! да!
Обратите внимание на тонкую проблему, которая у нас возникла: если выдается исключение, мы изменили элементы size
и capacity
, но все еще имеем старые buffer
.
Исправление очевидно, конечно: мы должны сначала изменить буфер, а затем размер и емкость. Конечно ...
Но "сложно" понять это правильно.
Я бы рекомендовал использовать альтернативный подход: создать класс неизменяемого массива (емкость должна быть неизменной, а не остальные) и реализовать метод swap
без исключений.
Тогда вы сможете гораздо проще реализовать семантику, подобную транзакции.