//------------------------------------------------------------------------------
// Мечта моего детства —
// создать "динамический массив"
// такой, чтобы не следить за памятью, ни при создании, ни при удалении,
// и снаружи никаких указателей.
// В конце файла — реализация этого класса, и соответствующие жалобы
//------------------------------------------------------------------------------
template <class T> class IList{
public:
int Length;
T *Items; //Указатель на сам массив
IList();
~IList();
IList operator = (IList);
T& operator [](int i) {return Items[i];}
void Add(T); //Добавить элемент к массиву
void Add(); //Добавить пустой элемент к массиву
void Clear(); //Отчискта массива
private:
};
//-------------------------------------------------------------------------------------
template <class T> IList<T>::IList(){
Length = 0;
}
//-------------------------------------------------------------------------------------
template <class T> IList<T>::~IList(){
delete Items;
}
//-------------------------------------------------------------------------------------
template <class T> IList<T> IList<T>::operator = (IList<T> data){
delete Items; //Удалить старый список
Length = 0;
for(int i=0; i<data.Length; i++)
Add(data.Items[i]); //Создать новый
return *this;
}
//-------------------------------------------------------------------------------------
template <class T> void IList<T>::Add(T data){
T *NewItems = new T[Length+1]; //Создать новый список (длинее существующего на 1)
for(int i = 0; i < Length; i++)
NewItems[i] = Items[i]; //Копировать старые данные в новые
NewItems[Length] = data; //Записать новые данные в конец нового списка
delete Items; //Удалить старый список
Items = NewItems; //Сохранить адресс нового списка
Length++; //Увеличить длинну списка на 1
}
//-------------------------------------------------------------------------------------
template <class T> void IList<T>::Add(){
T *NewItems = new T[Length+1];
for(int i = 0; i < Length; i++)
NewItems[i] = Items[i];
delete Items;
Items = NewItems;
Length++;
}
//-------------------------------------------------------------------------------------
template <class T> void IList<T>::Clear(){
delete Items;
Length = 0;
Items = 0;
}
//-------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender){
int i, j, c = 2;
IList <int> a;
for(i=0; i<c; i++)
a.Add(i);
for(i=0; i<a.Length; i++)
Memo1->Lines->Add(IntToStr(a[i]));
//До сюда работает, но если закрыть программу — ругается
//А дальше ругается сразу во время выполнения
IList <IList <int> > b; //двумерный массив
for(i=0; i<c; i++){
b.Add();
for(j=0; j<c; j++)
b[i].Add(j);
}
for(i=0; i<b.Length; i++)
for(j=0; j<b[i].Length; j++)
Memo1->Lines->Add(IntToStr( b[i][j] ));
}
//-------------------------------------------------------------------------------------
// Где-то, где я не понял, вызывается деструктор.
// Почему он вызывается?
// Еще вопрос — Если удалять динамический массив, будет ли вызываться
// деструктор для каждого его элемента?
//-------------------------------------------------------------------------------------
Здравствуйте Idler, Вы писали:
I>//------------------------------------------------------------------------------ I>// Мечта моего детства — I>// создать "динамический массив" I>// такой, чтобы не следить за памятью, ни при создании, ни при удалении, I>// и снаружи никаких указателей.
[scip]
I>//------------------------------------------------------------------------------------- I>// Где-то, где я не понял, вызывается деструктор. I>// Почему он вызывается?
Не понял. Просто так деструкторы не вызываются.
I>// Еще вопрос — Если удалять динамический массив, будет ли вызываться I>// деструктор для каждого его элемента?
Если сделать так, как сделал ты — нет. Если так, как посоветовал я — да.
Здравствуйте Idler, Вы писали:
I>Здравствуйте SergH, Вы писали:
SH>>Используй vector из STL I>STL — что это? (я не много чайник)
Standard Template Library — стандартная C++-ная библиотека шаблонов, поддерживающая векторы, списки и ещё много полезных вещей. На rsdn есть статья про STL.
I>[scip] — это что?
Это искажённое skip — пропуск, обход, скачёк.
SH>>Не понял. Просто так деструкторы не вызываются.
I>Items = new MyClass[256]; I>а в MyClass, допустим, выделялась память, и она удаляется деструктором
У тебя было написано: "Где-то, где я не понял, вызывается деструктор. Почему он вызывается?"
Для автоматических объектов (в стеке) деструктор вызывается когда они выходят из области видимости.
Для динамических объектов (в куче) — когда выполняется delete.
Для статических (глобальные или static) — когда завершается программа.
Насколько я знаю, больше нигде деструктор вызываться не может. Поэтому вопроса я не понимаю, и твоё пояснение мне не помогло.
I>//------------------------------------------------------------------------------
I>// Мечта моего детства -
I>// создать "динамический массив"
I>// такой, чтобы не следить за памятью, ни при создании, ни при удалении,
I>// и снаружи никаких указателей.
I>// В конце файла - реализация этого класса, и соответствующие жалобы
I>//------------------------------------------------------------------------------
I>template <class T> class IList{
I> public:
I> int Length;
I> T *Items; //Указатель на сам массив
I> IList();
I> ~IList();
I> IList operator = (IList); // *)
I> T& operator [](int i) {return Items[i];}
I> void Add(T); //Добавить элемент к массиву // *)
I> void Add(); //Добавить пустой элемент к массиву
I> void Clear(); //Отчискта массива
I> private:
I> };
I>//-------------------------------------------------------------------------------------
I>template <class T> IList<T>::IList(){
I> Length = 0;
Items = 0;
I> }
В конструкторе ВСЕГДА нужно проинициализировать ВСЕ используемые на чтение члены. Так здесь Items не проинициализирован, сдеовательно, все действия с ним в дальнейшем могут привести к краху. Дополнительно, такие классы (с внутренним выделением памяти) ТРЕБУЮТ наличия конструктора копирования (имеющим сигнатуру в твоём случае IList( const IList& ) ) для корректного копирования выделенной памяти.
Как правило, IList operator=(IList) — грубая ошибка (или неэффективная реализация) оператора присваивания, потому как будут вызваны 2 конструктора копирования класса IList — при передаче параметра и при выходе из оператора. Это тебе надо? Лучше IList& operator=(const IList&) без конструкторов копирования.
I>//-------------------------------------------------------------------------------------
I>template <class T> void IList<T>::Add(T data){
I> T *NewItems = new T[Length+1]; //Создать новый список (длинее существующего на 1)
I> for(int i = 0; i < Length; i++)
I> NewItems[i] = Items[i]; //Копировать старые данные в новые
I> NewItems[Length] = data; //Записать новые данные в конец нового списка
I> delete[] Items; //Удалить старый список
I> Items = NewItems; //Сохранить адресс нового списка
I> Length++; //Увеличить длинну списка на 1
I> }
Здесь та же проблема, что и с оператором присваивания. Лучше Add(const T& data) без конструктора копирования.
I>//-------------------------------------------------------------------------------------
I>template <class T> void IList<T>::Add(){
I> T *NewItems = new T[Length+1];
I> for(int i = 0; i < Length; i++)
I> NewItems[i] = Items[i];
I> delete[] Items;
I> Items = NewItems;
I> Length++;
I> }
I>//-------------------------------------------------------------------------------------
I>template <class T> void IList<T>::Clear(){
I> delete[] Items;
I> Length = 0;
I> Items = 0;
I> }
I>//-------------------------------------------------------------------------------------
I>//-------------------------------------------------------------------------------------
I>void __fastcall TForm1::FormCreate(TObject *Sender){
I> int i, j, c = 2;
I> IList <int> a;
I> for(i=0; i<c; i++)
I> a.Add(i);
I> for(i=0; i<a.Length; i++)
I> Memo1->Lines->Add(IntToStr(a[i]));
I> //До сюда работает, но если закрыть программу - ругается
I> //А дальше ругается сразу во время выполнения
I> IList <IList <int> > b; //двумерный массив
I> for(i=0; i<c; i++){
I> b.Add();
I> for(j=0; j<c; j++)
I> b[i].Add(j);
I> }
I> for(i=0; i<b.Length; i++)
I> for(j=0; j<b[i].Length; j++)
I> Memo1->Lines->Add(IntToStr( b[i][j] ));
I> }
Есть много кода, написанного хорошими программистами и не очень, по спискам, динамическим массивам — в MFC, ATL, STL.
Посмотри как реализуются такие классы в этих средствах и на их основе тренируйся.
Я знаю такие классы в ATL —
CComDynamicUnkArray в atl\include\atlcom.h
CSimpleArray, CSimpleValArray, CSimpleMap в atl\include\atlbase.h
Здравствуйте Idler, Вы писали:
I>Items = new MyClass[256]; I>а в MyClass, допустим, выделялась память, и она удаляется деструктором
RTFM!
для MyClass должны быть определены copy constructor и operator=.
иначе при выражениях типа a=b или func(T a) будет создаваться побайтовая копия исходного объекта. если внутри класса был указатель, то у тебя появляется две копии объекта с одним и тем уже указателем. при удалении копии дергается деструктор, который освобождает память. в результате указатель в исходном объекте указывает в никуда и при попытке его заюзать все может сильно упасть.
отсюда мораль — юзай stl и определяй copy constructor и operator=.
Взрывоопасная смесь new[]+memmove+delete[].
И как оно только у тебя работает, особенно на двумерных массивах!
Было, допустим 10 объектов TYPE в m_pData, их скопировали ПОБАЙТНО в pNewData через memmove(), удалили деструторами в delete[] m_pData и продолжаем ими пользоваться после m_pData = pNewData.
Это будет работать более-менее с классами без деструкторов, но с деструкторами, ИМХО, вылетит напрочь.
Обычно удаляют память просто через delete (void*) m_pData, чтобы не вызывать деструкторы.
Vi2>Взрывоопасная смесь new[]+memmove+delete[]. Vi2>И как оно только у тебя работает, особенно на двумерных массивах! Vi2>Было, допустим 10 объектов TYPE в m_pData, их скопировали ПОБАЙТНО в pNewData через memmove(), удалили деструторами в delete[] m_pData и продолжаем ими пользоваться после m_pData = pNewData.
когда их копируют из m_pData в pNewData, они копируются в pNewData, а в m_pData остаются, или нет? Их уже два экземпляра, не так ли?Если остаются, то delete[] m_pData должно работать корректно.
Спасибо, я учту замечания и проверю с TYPE c деструкторами — с простыми типами это работает хоть с пятимерными массивами, только Redimension можно делать только последнему измерению (как в SafeArray или бейсиковских массивах)
Vi2>Это будет работать более-менее с классами без деструкторов, но с деструкторами, ИМХО, вылетит напрочь.
Vi2>Обычно удаляют память просто через delete (void*) m_pData, чтобы не вызывать деструкторы.
Здравствуйте Al-Ko, Вы писали:
AK>когда их копируют из m_pData в pNewData, они копируются в pNewData, а в m_pData остаются, или нет? Их уже два экземпляра, не так ли?Если остаются, то delete[] m_pData должно работать корректно. AK>Спасибо, я учту замечания и проверю с TYPE c деструкторами — с простыми типами это работает хоть с пятимерными массивами, только Redimension можно делать только последнему измерению (как в SafeArray или бейсиковских массивах)
Введу псевдо-обозначения О(ptr) — объект О типа TYPE имеет указатель на динамически выделяемую память ptr (есть аналогия с классом AKVector), чтобы было видно влияние деструктора.
m_pData-> O0(ptr0),O1(ptr1),...,ON(ptrN), где N=m_nSize
TYPE* pNewData = newTYPE[nNewSize];
pNewData-> O'0(NULL),O'1(NULL),...,O'N(NULL),O'N+1(NULL), где N=m_nSize
memmove(pNewData, m_pData, m_nSize * sizeof(TYPE));
pNewData-> O0(ptr0),O1(ptr1),...,ON(ptrN),O'N+1(NULL), где N=m_nSize
delete [] m_pData;
~O0 освободил ptr0,~O1 освободил ptr1,...,~ON освободил ptrN, где N=m_nSize
m_pData-> O0(ptr0),O1(ptr1),...,ON(ptrN), где N=m_nSize
m_pData = pNewData;
m_pData-> O0(ptr0),O1(ptr1),...,ON(ptrN),O'N+1(NULL), где N=m_nSize
Если теперь попытаться освободить delete [] m_pData; — в деструкторе или методе Add — произойдёт следующее
~O0 будет освобождать ptr0 и вылетит по ексепшену (прерыванию).
Конечно, лучше бы представить это всё графически, но нет у меня такой возможности.
template <class T> IList<T> IList<T>::operator = (IList<T> data){
delete Items; //Удалить старый список
Length = 0;
for(int i=0; i<data.Length; i++)
Add(data.Items[i]); //Создать новый
return *this;
}
Плсле return *this содержимое data изменяется, видимо для него вызывается деструктор.
Re: Кому не лень ковыряться в чужом коде
От:
Аноним
Дата:
18.07.02 15:40
Оценка:
Здравствуйте Idler, Вы писали:
I>//------------------------------------------------------------------------------ I>// Мечта моего детства — I>// создать "динамический массив" I>// такой, чтобы не следить за памятью, ни при создании, ни при удалении, I>// и снаружи никаких указателей.
I>// В конце файла — реализация этого класса, и соответствующие жалобы I>//------------------------------------------------------------------------------ I>template <class T> class IList{ I> public: I> int Length; I> T *Items; //Указатель на сам массив
I> IList(); I> ~IList(); I> IList operator = (IList); I> T& operator [](int i) {return Items[i];} I> void Add(T); //Добавить элемент к массиву I> void Add(); //Добавить пустой элемент к массиву I> void Clear(); //Отчискта массива I> private: I> }; I>//------------------------------------------------------------------------------------- I>template <class T> IList<T>::IList(){ I> Length = 0; I> } I>//------------------------------------------------------------------------------------- I>template <class T> IList<T>::~IList(){ I> delete Items; I> } I>//------------------------------------------------------------------------------------- I>template <class T> IList<T> IList<T>::operator = (IList<T> data){ I> delete Items; //Удалить старый список I> Length = 0; I> for(int i=0; i<data.Length; i++) I> Add(data.Items[i]); //Создать новый I> return *this; I> } I>//------------------------------------------------------------------------------------- I>template <class T> void IList<T>::Add(T data){ I> T *NewItems = new T[Length+1]; //Создать новый список (длинее существующего на 1) I> for(int i = 0; i < Length; i++) I> NewItems[i] = Items[i]; //Копировать старые данные в новые I> NewItems[Length] = data; //Записать новые данные в конец нового списка I> delete Items; //Удалить старый список I> Items = NewItems; //Сохранить адресс нового списка I> Length++; //Увеличить длинну списка на 1 I> } I>//------------------------------------------------------------------------------------- I>template <class T> void IList<T>::Add(){ I> T *NewItems = new T[Length+1]; I> for(int i = 0; i < Length; i++) I> NewItems[i] = Items[i]; I> delete Items; I> Items = NewItems; I> Length++; I> } I>//------------------------------------------------------------------------------------- I>template <class T> void IList<T>::Clear(){ I> delete Items; I> Length = 0; I> Items = 0; I> }
При беглом осмотре показалось странным следующее:
1. Зачем Items и Length public???
Для Length лучше написать
int Size() const { return Length; }
и все.
Об Item вообще никто кроме класса знать не должен. Если же очень нужно получать указатель на буфер, то определить ф-ию типа
const T* GetPtr() const
и
T* GetPtr()
.
2. В операторе присваивания (с учетом замечаний, уже высказанных) все сломается, если присвоить a = a.
3. Метод Add() нафиг не нужен.
4. Неэффективно наращивать массив на 1 и каждый раз переаллокировать. Лучше завести буфер и наращивать его скачками ... типа 4,8,16 и т.п. как больше нравиться... :)
Неплохо бы разделять буфер массива и сам массив. Буфер --- реже переаллокировать, не уменьшать (только отдавать весь типа FreeBuffer(), или в деструкторе).
5. Неплохо бы написать
const T& operator[int i]() const;
6. В отладочных версиях в
operator[int i]()
неплохо бы проверять на соответствие границам массива (что-нибудь типа
assert( 0 <= i && i < Length );
).
7. Можно иметь разные классы массива для простых типов и для элементов с нетривиальными констр / дестр / операторами=.
Обрати внимание на значки &. Благодаря им ни при передаче sc в operator= ни при возврате значения из operator= копирование не требуется и конструктор копирования не вызывается. И, соответственно, не вызываются деструкторы.
Если конструктор копирования написан корректно (т.е. в твоём случае нужно выделить память, скопировать туда объекты), то отсутствие & приведёт только к низкой производительности, перерасходу памяти, но никаких катастроф не будет (точнее, я не вижу для них повода).
Но если в конструкторе копирования происходит просто почленное копирование (такое будет в конструкторе копирования генерируемом компилятором), то когда вызовется деструктор для sc освободится память, на которую указывает поле Items sc (это нормально) и поле Items исходного объекта (а это плохо).
Здравствуйте Idler, Вы писали:
I>Здравствуйте SergH I>Еше один вопросик.
I>template <class T> void IList<T>::Add(T& data){ I> T* NewItems = new T[Length+1]; I> for(int i = 0; i < Length; i++) I> NewItems[i] = Items[i]; I> NewItems[Length] = data; I> if(Items !=0) I> delete[] Items; I> Items = NewItems; I> Length++; I> } I>По завершению функции вызывается деструктор на Items — Why? (на NewItems — не вызывается)
Items — член класса IList имеющий тип T*? Тогда откуда (и какой) у него деструктор, если не секрет? И вызываеться здесь он не должен.
А приведённый код похож на правильный. Единственное, при каждом добавлении увеличивать длину на 1 — очень медленно. Нужно иметь две переменные available и length, первая отражает размер выделенной памяти, а вторая используемый размер. И проверку if (Items != 0) можно убрать, так как delete[] NULL безопастно.
Здравствуйте SergH, Вы писали:
SH>Items — член класса IList имеющий тип T*? Тогда откуда (и какой) у него деструктор, если не секрет? И вызываеться здесь он не должен.
Здравствуйте Vi2, Вы писали:
Vi2>Здравствуйте Al-Ko, Вы писали:
AK>>когда их копируют из m_pData в pNewData, они копируются в pNewData, а в m_pData остаются, или нет? Их уже два экземпляра, не так ли?Если остаются, то delete[] m_pData должно работать корректно. AK>>Спасибо, я учту замечания и проверю с TYPE c деструкторами — с простыми типами это работает хоть с пятимерными массивами, только Redimension можно делать только последнему измерению (как в SafeArray или бейсиковских массивах)
Vi2>Введу псевдо-обозначения О(ptr) — объект О типа TYPE имеет указатель на динамически выделяемую память ptr (есть аналогия с классом AKVector), чтобы было видно влияние деструктора.
Vi2>
m_pData->> O0(ptr0),O1(ptr1),...,ON(ptrN), где N=m_nSize
Vi2> TYPE* pNewData = newTYPE[nNewSize];
pNewData->> O'0(NULL),O'1(NULL),...,O'N(NULL),O'N+1(NULL), где N=m_nSize
Vi2> memmove(pNewData, m_pData, m_nSize * sizeof(TYPE));
pNewData->> O0(ptr0),O1(ptr1),...,ON(ptrN),O'N+1(NULL), где N=m_nSize
Vi2> delete [] m_pData;
Vi2>~O0 освободил ptr0,~O1 освободил ptr1,...,~ON освободил ptrN, где N=m_nSize
m_pData->> O0(ptr0),O1(ptr1),...,ON(ptrN), где N=m_nSize
Vi2> m_pData = pNewData;
m_pData->> O0(ptr0),O1(ptr1),...,ON(ptrN),O'N+1(NULL), где N=m_nSize
Vi2>
Vi2>Если теперь попытаться освободить delete [] m_pData; — в деструкторе или методе Add — произойдёт следующее Vi2>~O0 будет освобождать ptr0 и вылетит по ексепшену (прерыванию).
Vi2>Конечно, лучше бы представить это всё графически, но нет у меня такой возможности.