Модераторы: Daevaorn
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Как правильно хранить объекты в контейнере, ... STL 
:(
    Опции темы
mr.DUDA
  Дата 21.8.2003, 16:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


3D-маньяк
****


Профиль
Группа: Экс. модератор
Сообщений: 8244
Регистрация: 27.7.2003
Где: город-герой Минск

Репутация: 25
Всего: 232



Если хранить сами объекты, то будет проблема с:
Код
class CSome
{
public:
     void  *ptr;
     CSome() {ptr = new char[100];}
     ~CSome() {delete ptr;}
};

list <CSome> lst;

т.к. при добавлении объекта в список будет использован конструктор копирования, и существующий пойнтер просто скопируется в новый объект.
А если делать так, как нам советуют в руководстве по STL:
Код
class CCC
{
     int i;
};

void main()
{
     list<CCC*> lst;
     lst.push_back(new CCC);
     lst.push_back(new CCC);
     lst.push_back(new CCC);
     lst.erase(lst.begin(), lst.end());
}

то в этом примере получим утечку памяти.
Всё в том же руководстве по STL (Степанов, Менг Ли) есть пример как чистить память алгоритмом "release" (примеры release1 и release2):
Код
 vector<X*> v;
 v.push_back (new X (2));
 v.push_back (new X (1));
 v.push_back (new X (4));
 release (v.begin (), v.end ()); // Delete heap-based objects.

Но никакой шаблонной ф-ции "release" в STL нету !!! А каждый раз чистить весь контейнер циклом в деструкторе - неудобно и громоздко.

Помогите люди добрые, как же правильно хранить объекты в контейнере ?
withstupid.gif


--------------------
user posted image
PM MAIL WWW   Вверх
Nastya
Дата 21.8.2003, 17:14 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Комодератор
Сообщений: 1287
Регистрация: 27.3.2002
Где: Мариуполь

Репутация: 5
Всего: 44



А если создавать свой класс сразу со всеми кострукторвами, дистукторами, конструкаторами копирования и перегрузками оперраторов такизх как = и т.д.
Или такой вариант совсем не подходит?


--------------------
Что бы понять рекурсию, надо понять рекурсию

"Профессионал - это человек сделавший все возможные ошибки в очень узкой области". Н.Бор
PM MAIL   Вверх
mr.DUDA
Дата 21.8.2003, 17:33 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


3D-маньяк
****


Профиль
Группа: Экс. модератор
Сообщений: 8244
Регистрация: 27.7.2003
Где: город-герой Минск

Репутация: 25
Всего: 232



Подходит вроде, только я до сих пор не могу понять: что же надо перегружать -- оператор "=", конструктор копирования, или и то и другое ? Если и то и другое, то покатит ли такой вариант:
Код
class CSome
{
public:
    char  i[100];
    void operator= (CSome &xobj) {memcpy(i, xobj.i, 100);}
    CSome(CSome &xobj)  {*this = xobj;}     // вызов оператора "="
};

Я так раньше делал, чтобы не писать по 2 раза один и тот же код копирования.


--------------------
user posted image
PM MAIL WWW   Вверх
mr.DUDA
Дата 21.8.2003, 18:15 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


3D-маньяк
****


Профиль
Группа: Экс. модератор
Сообщений: 8244
Регистрация: 27.7.2003
Где: город-герой Минск

Репутация: 25
Всего: 232



Нашел выход:
Код
template <class ForwardIterator>
void sequence_delete(ForwardIterator first, ForwardIterator last)
{
    while (first != last)
         delete *first++;
}

template <class ForwardIterator>
void map_delete(ForwardIterator first, ForwardIterator last)
{
    while (first != last)
         delete (*first++).second;
}

// ...... example

class CSome
{
// .....
}

void main()
{
    list<CSome> lst;
    lst.push_back(new CSome);
    lst.push_back(new CSome);
    sequence_delete(lst.begin(), lst.end());
}

Это один умный чел написал. Так легче, чем писать операторы "=", по сто раз переприсваивать значения объектам и пр. mad.gif ...

Это сообщение отредактировал(а) mr.DUDA - 21.8.2003, 18:16


--------------------
user posted image
PM MAIL WWW   Вверх
RAN
Дата 21.8.2003, 19:40 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Экс. модератор
Сообщений: 709
Регистрация: 14.3.2003
Где: Щёлково Моск.обл.

Репутация: 5
Всего: 6



Будь осторожен. Надо не забывать удалять объект перед удалением элемента.
Код

list<CSome*>::iterator i = lst.begin();
delete *i;
lst.erase(i);


У меня вообще есть класс, у которого одно из полей - динамически создаваемый объект.
class CClass {
private:
int x1;
int x2;
CSomeObject *obj;
.........................
и т.д.
.........................
};

Проблемы я предвидел, но без ошибок всё равно не обошлось. Всё это у меня в vector помещается. А, как известно, vector неразрывно хранит в памяти элементы. Чтоб это обеспечить он использует оператор =. Мне пришлось реализовывать этот оператор. В нём приходится создавать копию динамического объекта. Короче, очень не эффективно. Я считаю, что это слабое место STL. Для хранения классов с динамическими объектами внутри лучше писать свой контейнер, учитывающий особенности класса.

PM MAIL ICQ   Вверх
mr.DUDA
Дата 21.8.2003, 20:50 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


3D-маньяк
****


Профиль
Группа: Экс. модератор
Сообщений: 8244
Регистрация: 27.7.2003
Где: город-герой Минск

Репутация: 25
Всего: 232



RAN, почему же. Если в деструкторе объекта X, содержащего указатель pY на объект Y, вызывать "delete pY", то какие могут быть проблемы и ошибки. А то, что объект хранится в vector'е, еще не обязывает реализовывать оператор "=" (вообще-то не только для вектора, но и для списка, словаря и т.п. нужно в свой объект поместить конструктор копирования, как я выяснил-таки).

Речь же идет о том, выгоднее ли хранить в контейнере не объекты, а указатели на (существующие) объекты, и что с этим сопряжено (необходимость вручную вызывать деструктор и освобождать память для каждого указателя в контейнере, перед уничтожением самого контейнера). Я предложил пользоваться довольно простыми алгоритмами "map_delete" и "sequence_delete", второй из которых подходит для векторов, списков и deques, а первый - для всех ассоциативных контейнеров.


--------------------
user posted image
PM MAIL WWW   Вверх
RAN
Дата 22.8.2003, 11:11 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Экс. модератор
Сообщений: 709
Регистрация: 14.3.2003
Где: Щёлково Моск.обл.

Репутация: 5
Всего: 6



Test that:
Код

#include <iostream.h>
#include <list>
using namespace std;

class CSomeClass {
private:
 int x;
 int *z;
public:
 void print()
  { cout << x << " " << *z << endl;}
 
 void Set(int val)
  { x = *z = val; }
 
 bool isCorrect()
  { return (x == *z); }
 
 CSomeClass()
  {
  z = new int;
  }
 
 ~CSomeClass()
                                               { delete z; }

};

int main()
{
list<CSomeClass> arr;
list<CSomeClass>::iterator el;
int i;
for(i = 0; i != 10; i++) {
 el = arr.insert(arr.end());
 el->Set(i);
}
for(el = arr.begin(); el != arr.end(); el++) {
 el->print();
 if( ! el->isCorrect() ) {
  cout << "Вот вам и ошибка!" << endl;
  break;
 }
}
return 0;
}

Надпись Вот вам и ошибка я, правда, так и не увидел. В debug выскакивает assert, а в релизе WinXP предлагает отправить отчёт в MicroSoft. В полне возможно, что в вашей реализации STL это будет работать, тогда замените везде слово list на vector и ошибка обязательно возникет smile.gif. Если же и тогда будет работать, то вставте перед циклом вывода оператор arr.erase(arr.begin()) (arr должен быть vector) - ошибка появиться на 100%

А вот лекарство, добавьте в определение класса это:
Код

 CSomeClass(const CSomeClass& other) {
  z = new int;
  *this = other;
  }

 CSomeClass& operator=(const CSomeClass& other) {
  x = other.x;
  *z = *other.z;
  return *this;
  }


И всё заработает.

ЗАДАНИЕ smile.gif Попробуйте объяснить причины ошибок.
PM MAIL ICQ   Вверх
mr.DUDA
Дата 22.8.2003, 14:40 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


3D-маньяк
****


Профиль
Группа: Экс. модератор
Сообщений: 8244
Регистрация: 27.7.2003
Где: город-герой Минск

Репутация: 25
Всего: 232



Цитата
Попробуйте объяснить причины ошибок

в итераторе вставки элемента в контейнер используется следующий порядок выделения памяти под новый элемент:
Код
iterator insert(iterator _P, const _Ty& _X = _Ty())
{
    _Nodeptr _S = _P._Mynode();
    _Acc::_Prev(_S) = _Buynode(_S, _Acc::_Prev(_S));
    _S = _Acc::_Prev(_S);
    _Acc::_Next(_Acc::_Prev(_S)) = _S;

    //  новый объект ("_X") был создан конструктором без параметров по
    // умолчанию ("_Ty()"); теперь он будет передан в allocator::construct
    allocator.construct(&_Acc::_Value(_S), _X);

    ++_Size;
    return (iterator(_S));
}

template<class _Ty> class allocator {
...
    void construct(pointer _P, const _Ty& _V)
   {_Construct(_P, _V); }   // вызов шаблона _Construct
...
};

//  что мы видим ? а то, что под элемент, который будет сохранён внутри
//  контейнера ("_P") выделяется память оператором "new", затем полученный
//  блок памяти инициализируется значением "_V", с помощью конструктора
//  копирования (!!!)
template<class _T1, class _T2> inline
void _Construct(_T1 _FARQ *_P, const _T2& _V)
{
    new ((void _FARQ *)_P) _T1(_V);
}


Если не задать конструктор копирования в своём классе, то ошибка появится в том случае, если:

  • класс элемента содержит и использует указатели на память, выделяемую и освобождаемую самим объектом
  • в контейнер, хранящий элементы такого класса, вставляется элемент, что приводит к вызову конструктора копирования по умолчанию (а этот просто сделает "memcpy" из одного объекта в другой)
  • так как объект "_X", созданный в итераторе "insert", автоматически удаляется при выходе из "insert", вызывается деструктор для "_X" (освобождение памяти в "CSomeClass::~CSomeClass() {delete z;}")
  • внутри элемента, хранящегося в контейнере, останутся указатели, связанные с памятью, выделенной не элементом а объектом "_X" (уже удалённым), получим источник ошибки
  • при удалении элемента из контейнера ("erase", или когда контейнер уничтожается) в деструкторе элемента производится попытка освободить несуществующий блок памяти -- вот и приплыли !!!!!


Вот и всё. Определив, наконец, конструктор копирования, избавимся от проблем "левых" указателей.

Ещё раз подчеркну, что подобные проблемы отсутствуют при хранении в контейнере не объектов, а указателей на объекты (для указателя не нужно определять конструктор копирования, правда ?)


--------------------
user posted image
PM MAIL WWW   Вверх
RAN
Дата 23.8.2003, 12:30 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Экс. модератор
Сообщений: 709
Регистрация: 14.3.2003
Где: Щёлково Моск.обл.

Репутация: 5
Всего: 6



Всё верно. Пять баллов. Я это написал в ответ на:
Цитата
Если в деструкторе объекта X, содержащего указатель pY на объект Y, вызывать "delete pY", то какие могут быть проблемы и ошибки
А в остальном ты прав. В самом первом топике этой темы ты написал класс CSome, так вот этот класс в ивестных мне реализациях STL использовать нельзя вообще, потому что :
Цитата

так как объект "_X", созданный в итераторе "insert", автоматически удаляется при выходе из "insert", вызывается деструктор для "_X" внутри элемента, хранящегося в контейнере, останутся указатели, связанные с памятью, выделенной не элементом а объектом "_X" (уже удалённым), получим источник ошибки.

Сразу скажу, что insert вызывется и при вызове push_back. Так что использование любого метода для добавления элементов приведён к ошибке.

Цитата
подобные проблемы отсутствуют при хранении в контейнере не объектов, а указателей на объекты (для указателя не нужно определять конструктор копирования, правда ?)

ПРАВДА. Но надо быть осторожным smile.gif Представь, что ты скопируешь указатель на объект куда-то и тогда при удалении объект будет удаляться дважды. Если использовать только insert-функции и erase-функции вместе с delete, то будет всё правильно (запутанно сказал).

PM MAIL ICQ   Вверх
mr.DUDA
Дата 23.8.2003, 21:20 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


3D-маньяк
****


Профиль
Группа: Экс. модератор
Сообщений: 8244
Регистрация: 27.7.2003
Где: город-герой Минск

Репутация: 25
Всего: 232



Если хранить в контейнере указатели на объекты, сама логика исходников меняется в лучшую сторону (приходится самостоятельно отслеживать ситуации, когда объект создается, копируется и уничтожатеся).

Это добавляет ответственности, зато намного снижает количество ошибок.


--------------------
user posted image
PM MAIL WWW   Вверх
RAN
Дата 23.8.2003, 21:40 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Экс. модератор
Сообщений: 709
Регистрация: 14.3.2003
Где: Щёлково Моск.обл.

Репутация: 5
Всего: 6



А вот с этим не согласен. Меняется всё в худшую сторону. Потому как самому лучше ничего не отслеживать - это потенциальный источник ошибки. Всегда, когда это возможно, объкты надо уничтожать в деструкторе, а создавать в конструкторе, или создавать в начале какого-то блока, а уничтожать в его конце. Хранение указателей в контейнере оправдано тогда, когда объекты разных классов далжны храниться в одном контейнере или не известно какого класса объекты будут храниться (у меня второй случай).

Если хранить в контейнере указатели, то это увеличивает расход памяти, сильно увеличивает возможность run-time ошибок, которые могут неожиданно проявить себя в готовом и уже проданном проекты (потом в запарке без сна будешь править, у людей ведь работа стоит). При всём при этом выигрышь в скорости будет не значителен, потому что память будет выделяться под хранение указателей и эти указатели будут также перемещаться с одного места на другой. Это на мой взгляд - проблема STL.
PM MAIL ICQ   Вверх
Fantasist
Дата 24.8.2003, 08:10 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Лентяй
***


Профиль
Группа: Участник Клуба
Сообщений: 1517
Регистрация: 24.3.2002

Репутация: 4
Всего: 41



Хранить указатели в контейнерах неудобно, потому как надо самому выделять и удалять память. Например твой sequence_delete удаляет объекты из контейнера, но ничего не делает с самим контейнером - получаешь контейнер с инвалидными указателями. Если ты скопируешь указатели в другой контейнер, то непонятно, кто их должен удалять. Отлавливать подобные ошибки очень неприятно. Однако действительно существуют задачи, когда один и тот же объект нужно хранить в нескольких контейнерах и причем создание копии объекта недопустимо, так как он связан с другими объектами. В этом случае на помощь приходят умные указатели в различных вариантах. Например master pointer:

Код

template <class T>
class m_ptr  
{
private:
   T* data;
public:
  m_ptr(): data(new T()) {};
  m_ptr(const T* Data): data(Data) {};
  m_ptr(const& m_ptr<T> ptr): data(new T(*ptr.data)) {};  
  ~m_ptr() {delete data;};

 operator T*() {return  data;};
 T* operator->() {return data;};
 m_ptr<T>& operator=(const& m_ptr<T> ptr) {delete data; data=new T(*ptr.data); };
}


Eдинственное требование от T - конструкрот копирования. В принципе его можно поменять на operator=().

Использование элементарно. Определяется контейнер, который будет "мастером" для объектов. То есть который их будет хранить и удалять.

Код

typedef m_ptr<CCC> ptrCCC;

{
    vector<ptrCCC> vec;
    vec.push_back(ptrCCC(new CCC));
    vec.push_back(ptrCCC());
   
    vec[0]->SetVal(val);

    CCC* p=vec[1];
    int vl=p->GetVal();
   
    list<CCC*> lst;
    lst->push_back(p);
}


Конечно, для частых реаллокаций это не очень эффективно. Но частые реаллокации будут возникать, если часто вставлять/удалять элементы чего-нибудь типа vector. Ежели использовать list или map, то реаллокаций не будет. Для вектора, пожалуй, подойдет нечто типа auto_ptr<>.

Но если у тебя только один контейнер, то очень реккомендую просто определить конструктор копирования и не мучаться более. Это будет гораздо безопаснее и понятнее. На эту тему много можно поизобретать в зависимости от задачи. Очень большую роль играет выбор контейнера. Подходы для vector, list и map могут весьма отличаться.


--------------------
Волны гасят ветер...
PM MAIL   Вверх
Fantasist
Дата 24.8.2003, 08:14 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Лентяй
***


Профиль
Группа: Участник Клуба
Сообщений: 1517
Регистрация: 24.3.2002

Репутация: 4
Всего: 41



Цитата
Если хранить в контейнере указатели на объекты, сама логика исходников меняется в лучшую сторону (приходится самостоятельно отслеживать ситуации, когда объект создается, копируется и уничтожатеся).


И это называется улучшением логики!!! Работал я над кодом с такой логикой - в нем же что-то менять страшно: не понятно где и как это может откликнуться.


--------------------
Волны гасят ветер...
PM MAIL   Вверх
mr.DUDA
  Дата 24.8.2003, 11:07 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


3D-маньяк
****


Профиль
Группа: Экс. модератор
Сообщений: 8244
Регистрация: 27.7.2003
Где: город-герой Минск

Репутация: 25
Всего: 232



Хех, наконец-то народ откликнулся smile.gif

Я для этого и назвал топик "как правильно хранить ?", чтобы узнать, как кто делает adv/225.gif, а чтобы узнать как делает кто-то другой, приходится показывать как делаешь сам wink.gif.

Выводы однозначные: безопаснее хранить сами объекты, чем указатели. Для хранения объекта класса X нужно определить конструктор копирования для X и переопределить оператор "=".

В отдельных случаях, когда необходим полный контроль над "жизненным циклом" объекта, или нужно минимизировать количество реаллокаций, можно допустить хранение пойнтеров вместо объектов.


--------------------
user posted image
PM MAIL WWW   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "С++:Общие вопросы"
Earnest Daevaorn

Добро пожаловать!

  • Черновик стандарта C++ (за октябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика(4.4мб).
  • Черновик стандарта C (за сентябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика (3.4мб).
  • Прежде чем задать вопрос, прочтите это и/или это!
  • Здесь хранится весь мировой запас ссылок на документы, связанные с C++ :)
  • Не брезгуйте пользоваться тегами [code=cpp][/code].
  • Пожалуйста, не просите написать за вас программы в этом разделе - для этого существует "Центр Помощи".
  • C++ FAQ

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.0916 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.