С++ удаление ответственность
Возникла проблемка: существует некий класс, назовем его TInfo, у него есть метод, который возвращает список объектов GetList() - его задача вернуть список объектов (в любом контейнере).
Вопрос заключается в том, кто будет ответственнен за удаление этого контейнера?
Если создавать контейнер в функции, то передав его наружу не получится контролировать его. Делать умный указатель внутри TInfo не очень хороший вариант - вдруг будет два запроса GetList подряд (не делать же массив умных указателей).
У меня есть такой вариант: GetList принимает по ссылки контейнер и заполняет его, таким образом видно, что ответственность лежит не на классе, а на внешней системе.
Хочется знать как вообще в С++ решается проблема ответственности при возвращении из функции сложных типов? И насколько плох/хорош мой вариант с передачей контейнера по ссылки для последующего заполнения?
Не понятно, что возвращает твоя ф-ция, чем именно заполнен контейнер?
Если при каждом вызове ф-ции контейнер заполняется вновь получаемыми значениями (данные каждый раз перерасчитываются), то логичнее, чтоб их удалял тот, кто их получает.А ещё правильнее, возложить эту ответственность на сам контейнер, например, использовать обычный контейнер, содержащий данные по значению, а не по указателю.
Если же ф-ция возвращает набор референсов на некоторые хранимые данные, например, есть общий набор данных, а твоя ф-ция возвращает некоторую выборку из этого набора, то распоряжаться данными должен тот, кто их хранит.
По моему С++ на то и С++, что контроль памяти под динамические объекты целиком лежит на программисте.
Хороший вариант, но тогда внутри функции GetList() этот контейнер нужно предварительно очищать - во избежании потери памяти.
Но чтобы вообще не парится - есть варинат:
- объявить в классе TInfo статический vector<map<void*,int>> - и каждый раз, когда ваша функция "выпускает наружу" динамический объект, сохранять в вектор(Мап(его адрес, его размер)).
- в статическом деструкторе прогнать весь вектор и освободить память по указателям в заданных размерах
Таким образом ваша программа всегда будет корректно завершена.
По моему С++ на то и С++, что контроль памяти под динамические объекты целиком лежит на программисте.
Ну а как же smart pointers ?
Но чтобы вообще не парится - есть варинат:
- объявить в классе TInfo статический vector<map<void*,int>> - и каждый раз, когда ваша функция "выпускает наружу" динамический объект, сохранять в вектор(Мап(его адрес, его размер)).
- в статическом деструкторе прогнать весь вектор и освободить память по указателям в заданных размерах
Таким образом ваша программа всегда будет корректно завершена.
1. Автор топика говорит про контейнер. Каким боком тогда здесь void* ?
2. Старайтесь (категорически) не использовать void*. Без него можно всегда (99.9%) обойтись.
3. В C++ нет статических деструкторов.
4. А если вызовов этой функции миллион? Будем добавлять память в слоты материнской платы?
Если при каждом вызове ф-ции контейнер заполняется вновь получаемыми значениями (данные каждый раз перерасчитываются), то логичнее, чтоб их удалял тот, кто их получает.
Параллельный вопрос (про COM)
есть метод СОМ объекта, который возвращает VARIANT*
т.е.:
{
VARIANT val;
val.vt = VT_BSTR;
val.bstrVal = ::SysAllocString(_T("Come string"));
*Value = &val;
return S_OK;
}
Вообще, тут нужен какой-то код, так как не ясен контекст. А так можно сказать, что контейнер должен удалиться сам, как выйдет из области видимости.
Лучше по указателю. Так будет понятнее, что контейнер поменяется. Поэтому обычно, используются константные ссылки, чтобы не запутаться.
Но тут опять ничего не понятно без кода. Если TInfo что-то насоздавал динамически, то как об этом узнает внешняя система? Опять же лучше, чтобы контейнер сам себе выделял память, а потом сам и удалил.
{
stack<void*> Pointer;
public:
Control(void* P){this->Pointer.push(P);}
~_GC()
{
while(!Pointer.empty()){ //-572662307 - число свободы
if(*(int*)Pointer.top() != -572662307){
cout<<Pointer.top()<<" -> free"<<endl;
free(Pointer.top());}
Pointer.pop();}
}
}mGC;
int main()
{
int *F = new int();
mGC.Control(F);
double *D = new double();
mGC.Control(D);
mGC.Control(D);
}
Есть смысл все указатели, которые потенциально могут повиснуть загружать в mGC - в конце работы программы он освободит все ссылки из стека, если они не были free ранее.
Какой-то неудачный пример...
1. Зачем нужен контейнер, содержащий указатели на данные неизвестного типа? Я бы понял, если бы они были наследниками одного общего базового класса, в котором мы будем использовать одинаковые функции.
2. Зачем нужны указатели на такие простые типы? Что мы экономим тут?
3. Зачем нужно "число свободы"? Типа, данные, равные этому числу не будут удаляться? А смысл?
4. Почему используется free, а не delete? Не симметрично как-то.
5. Переменная, на которую указывает D, удалится 2 раза?
Запихнуть любой тип.
Это пример. Можно более сложные указатели передавать.
чтоб на ваш пункт 5 ответ был "нет".
delet вызывает деструктор типа. Но если деструктор перекрыт private - облом и ошибка.
Нет.
В чем фокус? То есть, если я удалил переменную, то указатель на нее вернет такое число? Не знал. Можно ссылки на такую информацию?
А главное, что и практика показывает, что нет такого числа. В общем, непонятный "сборщик мусора"...
Ну, а зачем лезть туда, где запрещено удалять таким образом? Деструктор защитили из каких-то соображений, а не просто так. Может тут лучше исключением воспользоваться?
Вы правы - я ошибался.
1. Заточен под конкретный тип (семейство классов в лучшем случае).
2. Должен объявляться первым в своей области видимости.
class GC
{
list<T*> pointers;
public:
T* GetPointer()
{
pointers.push_back(new T());
return pointers.back();
}
~GC()
{
while (!pointers.empty())
{
delete pointers.front();
pointers.pop_front();
}
}
};
struct MyClass
{
MyClass(){cout << "MyClass()" << endl;}
~MyClass(){cout << "~MyClass()" << endl;}
};
GC<MyClass> mGC;
GC<double> dGC;
GC<int> iGC;
int main()
{
double *dcp = dGC.GetPointer();
int *icp = iGC.GetPointer();
{ // локальный контейнер с указателями
GC<MyClass> local_mGC;
MyClass *mcp[3];
for(int i = 0; i < 3; ++i)
mcp = local_mGC.GetPointer();
}
cout << "in main" << endl;
MyClass *mcp[3];
for(int i = 0; i < 3; ++i)
mcp = mGC.GetPointer();
return 0;
}
Лучше по указателю. Так будет понятнее, что контейнер поменяется. Поэтому обычно, используются константные ссылки, чтобы не запутаться.
А я бы рекомендовал передавать по ссылке. Меняется или нет контейнер, должно следовать из названия ф-ции и упоминанием слова const.
А вот указатель по-хорошему придется ещё и на 0 проверять.
IMHO указатели стоит применять только там, где без них не обойтись. В остальных случаях - ссылки.
Взгляните на уже изобретенные велосипеды:
http://www.boost.org/doc/libs/1_36_0/libs/ptr_container/doc/ptr_container.html
А вот указатель по-хорошему придется ещё и на 0 проверять.
Тут рассматриваются тонкости, которые можно понять только когда много раз наступишь на те или другие грабли. Может у меня будет больше случаев, когда я вроде передавал переменную в функцию по значению, а оказалось, что передалось по ссылке. Может больше будет случаев, когда я передам вместо "правильного" указателя ноль или "удаленный" указатель (извините за термины).
В обоих случаях может быть заложена мина замедленного действия.
http://www.boost.org/doc/libs/1_36_0/libs/ptr_container/doc/ptr_container.html
Ну, я свой велосипед изобрел ради шутки. Не думал, что такое может пригодиться. А вообще, за boost надо будет как-нибудь серьезно взяться. Осталось немного "подрасти" и придумать себе хороший стимул.
То, что увидел мельком (клоны и прочее) почему-то напомнило Java.
Вообще дело вот как обстоит: функция ищет в системе определенные классы и возвращает ссылки на них, т.е. std::list<TSomeClass*>.
Получается надо удалить только сам контейнер.
to Kogrom: а как сделать, чтобы удалялось при выходе из области видимости? Поломал голову, но так и не придумал. Если только из функции возвращать auto_ptr<list<TSomeClass*>>, страшновато выглядит :)
P.S. спасибо всем за ценные замечания по поводу передачи контейнера в функцию :) Кое-чего я не учел :)
to Kogrom: а как сделать, чтобы удалялось при выходе из области видимости? Поломал голову, но так и не придумал. Если только из функции возвращать auto_ptr<list<TSomeClass*>>, страшновато выглядит :)
Я за него :)
Если борешься за производительность, то контейнер для заполнения лучше передавать снаружи в качестве аргумента ф-ции. Так у тебя, кстати, появляется возможность ещё и возвращать результат выполнения операции, например true/false.
Если уж так нужен указатель, то сделай typedef:
typedef auto_ptr<list<TSomeClass*>> MyListPtr;
Если уж так нужен указатель, то сделай typedef:
typedef auto_ptr<list<TSomeClass*>> MyListPtr;
Вот так мне очень нравится :) И это выглядит более правильно с точки зрения привычного положения вещей - функция просто возвращает список, а не просит список для заполнения.
Кстати не думаю, что вариант с умным указателем будет "медленнее", особенно с учетом того, что при передаче контейнера в функцию надо делать ряд проверок. Производительность у меня на первом плане (система моделирования) так что надо будет подумать, что лучше :)
Получается надо удалить только сам контейнер.
to Kogrom: а как сделать, чтобы удалялось при выходе из области видимости? Поломал голову, но так и не придумал. Если только из функции возвращать auto_ptr<list<TSomeClass*>>, страшновато выглядит :)
Пять минут думал над вопросом, но так и не понял, что спрашивалось. Если переменная локальная (автоматическая), то она сама удалится при выходе из области видимости. Ничего не надо делать, если не использовать функции типа new.
Я спросил и сам же предложил варинт... просто думал может как-то по-другому можно :)
Конечно, можно. Не пойму, зачем тут нужен auto_ptr.
{
list<TSomeClass*> myList;
// используем myList;
}
// Область видимости закончилась - myList разрушился сам
Ну, можно извратиться и написать что-то типа:
{
list<TSomeClass*> *myListPtr = new list<TSomeClass*>(0);
// используем myListPtr;
}
// Область видимости закончилась - *myListPtr не разрушился
Но зачем так делать? Не вижу логики.
Видимо, GreenRiver, имел в виду иное:
{
auto_ptr< list<TSomeClass*> > myListPtr( new list<TSomeClass*>(0) );
return myListPtr;
}
только это не совсем красиво и удобно
я бы все же сделал так:
Да, именно так! Неудобство и правда в таком варианте есть: возвращается не простой указатель - и если это будет например vector, то не получится воспользоваться оператором [] (только если в явном виде: operator[]() ).
Vector[0]; // ошибка, что и понятно в общем-то
Vector->operator [](0); // только так
Так что остановлюсь на передаче контейнера внутрь функции для заполнения.
Vector[0]; // ошибка, что и понятно в общем-то
Vector->operator [](0); // только так
Так что остановлюсь на передаче контейнера внутрь функции для заполнения.
можно и по другому (с обычными указателями - точно)
(*Vector)[0]; //
Но тоже мало красивого.
Проверил - можно и с "умными". Все же покрасивее, чем operator[](0) :)