C++ Вызов деструкторов объектов и очистка памяти после realloc
Всем привет! Вопрос чисто теоретический: Есть ли в С++ аналог realloc из ванильного Си? Чтобы можно было менять размеры массива? И смежный вопрос: будут ли правильно(т.е. с вызовом деструкторов) удаляться объекты при вызове delete[] array,если перед этим было array=realloc(....) ? И если нет, то как решить эту проблему? P.S. я знаю,что есть волшебный std::vector, но меня очень интересует реализация без примочек из STL.
Ответы (2 шт):
Есть ли в С++ аналог realloc из ванильного Си? Чтобы можно было менять размеры массива?
Аналога нет. Но в большинстве ситуаций realloc деградирует до malloc/memcpy/free. Выделяется новый блок памяти, содержимое старого блока копируется в его начало, старый блок памяти удаляется.
будут ли правильно(т.е. с вызовом деструкторов) удаляться объекты при вызове delete[] array,если перед этим было array=realloc(....) ? И если нет, то как решить эту проблему?
Эту проблему нельзя решить, когда она возникла. Если вы не знаете как был выделен кусок памяти, у вас нет способа его удалить или изменить его размер. С самого начала должна быть строгая дисциплина. Для каждого указателя вы знаете как он был выделен и применяете только соответствующие функции. Или calloc/malloc/realloc/free, или new[]/delete[] (или new/delete если речь не о массивах). Указатель полученный вызовом в стиле C может обрабатываться только в стиле C. Указатель полученный вызовом в стиле C++ может обрабатываться только в стиле C++.
я знаю,что есть волшебный std::vector, но меня очень интересует реализация без примочек из STL.
Он не волшебный. Он написан на C++ и хорошо показывает как из низкоуровнего управления памятью получить высокоуровневый безопасный интерфейс. И без дополнительных накладных расходов в большинстве ситуаций.
Чтобы понять, почему нельзя перемешивать "сишные" malloc()/realloc()/free() и "плюсовые" new()/delete()/new[]()/delete[](), надо понимать принципиальную разницу между этими двумя способами работы с динамической памятью. А разница эта заключается в том, что malloc()/realloc()/free() работают с голой памятью, в то время, как операторы new и delete имеют дело уже с объектами, для которых дополнительно вызываются цепочки конструкторов и деструкторов.
Когда создаётся динамический массив объектов, то оператор new[] делает следующее:
- Выделяет память под массив (для этого, как правило, вызывается обычная
malloc()). Если память выделить не удалось, бросает исключениеstd::bad_alloc(). - Если объекты заданного типа имеют нетривиальный конструктор (отличный от того, что по умолчанию), то вызывает в цикле конструктор для каждого объекта в массиве.
Оператор delete[], соответственно, действует наоборот:
- Если объекты заданного типа имеют нетривиальный деструктор, то в обратном цикле (справа налево) вызывает деструктор для каждого объекта.
- Освобождает память (вызвав
free()).
И вот тут должен возникнуть вопрос: а откуда оператор delete[] собственно узнаёт длину массива? Ведь в качестве аргумента этому оператору передаётся только указатель! А ответ прост: если существует нетривиальный деструктор, то оператор new[] выделяет память не только под массив, но и под его длину, определённым образом сохраняет эту длину в выделенную область и возвращает смещённый указатель! И это только одна из причин, почему нельзя вызывать free() и realloc() для указателя, который был получен с помощью new[] - потому что этот указатель может оказаться смещённым по отношению к тому адресу, который возвратила malloc() внутри оператора new[]!
И так: существует ли аналог realloc() для вариантов с new[] и delete[]? Ну например, какой-нибудь гипотетический оператор renew Type[new_length]. Увы, нет такого оператора. Можно ли реализовать нечто похожее без использования std::vector? Можно, но для этого придётся всё равно написать собственный аналог std::vector.
Ниже я набросал демку, реализующую шаблонный класс CDynamicArray, который имеет три основных метода: New(), ReNew() и Delete(), а также перегружает некоторые операторы, мимикрируя под обычный массив. ReNew(), как нетрудно догадаться, действует, как аналог realloc() Можно сказать, что прога демонстрирует принципы, по которым работает сам std::vector, так что думаю, полезно будет к ознакомлению. И да, может содержать ошибки, хоть я и отладил (если кто найдёт, просьба указать).
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <new>
//Шаблонный класс-велосипед, реализует динамический массив, похожий по поведению на std::vector
template <typename TData>
class CDynamicArray
{
TData* m_pArray;
size_t m_length;
void _copy_array(CDynamicArray* pSrcArray)
{
m_length = pSrcArray->m_length;
if (m_length)
{
TData* pCopy = (TData*)malloc(m_length*sizeof(TData));
if (!pCopy) throw std::bad_alloc();
for (TData *pDst=pCopy, *pSrc=pSrcArray->m_pArray, *pEnd = pCopy+m_length; pDst<pEnd; ++pDst,++pSrc)
{
//вызываем конструктор копирования (palacement new)
new(pDst) TData(*pSrc);
}
m_pArray = pCopy;
return;
}
m_pArray = nullptr;
}
void _delete()
{
TData* pDst = m_pArray + m_length;
while (pDst > m_pArray) (--pDst)->~TData(); //вызываем деструктор напрямую
free(m_pArray); //освобождаем память
}
public:
CDynamicArray(): m_pArray(nullptr), m_length(0) {}
template <typename... TArgs> CDynamicArray(size_t length, TArgs... args): m_length(0) {New(length,args...);}
CDynamicArray(CDynamicArray& rSrcArray) {_copy_array(&rSrcArray);}
CDynamicArray& operator=(CDynamicArray& rSrcArray) {_delete(); _copy_array(&rSrcArray); return *this;}
~CDynamicArray() {_delete();}
template <typename... TArgs> void New(size_t length, TArgs... args)
{
if (m_length) _delete();
if (length)
{
TData* pArray = (TData*)malloc(length*sizeof(TData));
if (!pArray) throw std::bad_alloc();
for (TData *pDst=pArray, *pEnd = pArray+length; pDst<pEnd; ++pDst)
{
//вызываем конструктор (palacement new)
new(pDst) TData(args...);
}
m_pArray = pArray;
m_length = length;
return;
}
m_pArray = nullptr;
m_length = 0;
}
template <typename... TArgs> void ReNew(size_t new_length, TArgs... args)
{
if (new_length != m_length)
{
TData* pNewArray = (TData*)malloc(new_length*sizeof(TData));
if (!pNewArray) throw std::bad_alloc();
size_t min_length = (new_length > m_length)? m_length: new_length;
TData* pSrc = m_pArray;
TData* pDst = pNewArray;
TData* pDstEnd = pNewArray + min_length;
//копируем объекты в новую область
for (; pDst<pDstEnd; ++pDst,++pSrc) new(pDst) TData(*pSrc);
if (new_length > m_length)
{
//создаём новые объекты
pDstEnd = pNewArray + new_length;
for (; pDst<pDstEnd; ++pDst) new(pDst) TData(args...);
}
//удаляем старый массив
_delete();
m_pArray = pNewArray;
m_length = new_length;
}
}
void Delete()
{
_delete();
m_pArray = nullptr;
m_length = 0;
}
TData* GetArray() {return m_pArray;}
size_t GetLength() {return m_length;}
TData& operator[](int i) {assert ((i>=0) && (i<m_length)); return m_pArray[i];}
operator TData*() {return m_pArray;}
TData* operator+(int i) {return m_pArray + i;}
};
//тестовый класс
class CTest
{
int m_val;
public:
CTest(int val): m_val(val) {printf("Constructor for %d\n", m_val);}
//CTest(CTest& rSrc): m_val(rSrc.m_val) {printf("Copiing %d\n", m_val);}
~CTest() {printf("Destructor of %d\n", m_val);}
int GetVal() {return m_val;}
};
int main()
{
CDynamicArray<CTest> arr, arr2;
arr.New(4, 44);
printf("Array New:\t\t");
for (int i=0; i<arr.GetLength(); ++i) printf("%d ", arr[i].GetVal());
puts("\n");
arr.ReNew(7, 77);
printf("Array ReNew+:\t\t");
for (CTest* p = arr; p<arr+arr.GetLength(); ++p) printf("%d ", p->GetVal());
puts("\n");
arr.ReNew(2, 0);
printf("Array ReNew-:\t\t");
for (int i=0; i<arr.GetLength(); ++i) printf("%d ", arr[i].GetVal());
puts("\n");
arr2 = arr;
arr.Delete();
printf("Array copy:\t\t");
for (int i=0; i<arr2.GetLength(); ++i) printf("%d ", arr2[i].GetVal());
puts("\n");
arr2.Delete();
return 0;
}
Хочу обратить внимание, что и здесь мы не можем использовать функцию realloc(), т.к. при переносе данных в новую область памяти, старая область становится неопределена и мы не можем вызвать деструкторы для старых копий объектов. И даже если в случае уменьшения длины массива не гарантируется, что не будет выполнен перенос памяти. Так что остаётся только одно: создавать новую область, копировать объекты из старой области, удалять старую область (с вызовом деструкторов).