Проблема с библиотекой LinkedList.h
- Войдите на сайт для отправки комментариев
Ср, 28/06/2017 - 00:01
Понадобились мне связанные списки. Гугл сходу предложил библиотеку LinkedList.h. Которой я и поспешил воспользоваться. Однако, убился о такую вот штуку:
#include<LinkedList.h>
class firstType{
};
class secondType{
public:
LinkedList<firstType*> list;
secondType(){
list = LinkedList<firstType*>();
}
secondType doSomething(){
firstType* tmp=new firstType();
list.add(tmp);
return *this;
}
};
void setup() {
Serial.begin(9600);
secondType* v=new secondType();
v->doSomething();
Serial.println(v->list.size());
Serial.println(v->list.get(0)==NULL);
}
void loop() {}
На выходе получается 1 1, т.е. элементов в списке 1 и этот единственный элемент суть есть ничто.
Однако, если убрать строчку
return *this;
то вывод будет 1 0, т.е. список уже вполне адекватно себя чувствует.
Как такое может быть? Помогите ламеру, друзья, а то всю голову сломал уже...
А Вы понимаете, что делает
return*this; ?Попробуйте поставить отладочные печати во все методы и в конструктор (после строк 3, 10 и 13) и понаблюдайте за поведением.
Я не уверен, что правильно понимаю, что делает return *this, вроде как пишут, что он должен клонировать объект. Проверить сейчас не могу, ибо на работе, но предположу, что конструкторы firstType и secondType должны вызваться по два раза, метод класса один раз. Не так?
Придёте домой - исследуете.
Это, конечно, да. Но если вы в курсе проблемы, то, может быть, в двух словах поясните, что происходит? Я просто совсем не понимаю, каким образом конструкция return *this убивает существующий экземпляр класса... Слишком долго сижу на C#, видимо.
Знаете, Вот Вы разберётесь и будете знать прочно, на уровне понимания. Я любые мои объяснения дадут поверхностное "понимание на раз". Работайте сами.
Ваше право. Благодарю в любом случае.
А Вы понимаете, что делает
return*this; ?То же стало интересно. Я вроде понимаю, что делает
return*this;.Однако непонятно - doSomething() вызывается в void контексте и возвращаемое значение отбрасывается - почему это меняет содержимое экземпляра v?Поговорим, когда ТС поставит печати и расскажет, что получилось.
Кстати, ТС, для большей ясности, неплохо бы ещё поставить печать в конструкторе и деструкторе класса (перед строками 117 и 128). Думаю, так понятнее будет.
Я читаю и просто кожей чувствую, как у меня прокачивается скилл коррекционной педагогики.
Я остановлю этот праздник души и именины сердца. Просто из вредности... ;)
ТС! Строчка №13 ... хм... ну а какая же еще могла быть???
выглядит так:
secondType doSomething(){а должна так:
secondType& doSomething(){Понятна ли Вам разница, о мастер Си-Шарпа? ;) ;) ;)
-----------------------
Надеюсь на то, что педагогическую практику не испортил? ТС, как мне кажется, все равно не поймет, почему так происходит. Не поймет без подсказок Евгения.
-----------------------
Еще добавлю, что я издеваюсь по-доброму. Я сам не сильно люблю С++ за такие вот, неочевидные моменты.
Оно красиво, но не очевидно, что удобно. После моей подсказки, если знаете основы С++, то догадаетесь, если не догадались - не пользуйтесь такими конструкциями, там миллионы подводных камней.
wdrakula,
да тут до хрена всего, просто хочется, чтобы человек сам дошёл. Например, как соотносятся строки 8 и 11? Создаём экземпляр - выбрасываем нахрен, создаём новый, авось лучше будет! Я уж не говорю, про запрос памяти. Кстати, о памяти - её болезную под элемент списка сперва освобождаем, а потом кидаемся элемент печатать.
Хотя, зря всё это. Я хотел подсказать ТС про печати, а потом (в прорядке интерпретаии резальтатов) навести на литературу - самому доходить всегда полезнее.
Все сегодняшние примеры с непониманием у ТС - это мины, заложенные в С++ самим фактом неявных вызовов конструкторов и деструкторов.
Я не Архат, чтобы спорить с создателями языка ;), но ИМХО это не правильная "фича". Если мне нужно высокоуровневое программирование то есть Питон, или C#, для поклонников Винды. Си - язык низкоуровневого программирования. А С++ ушел далеко от Си - реально очень далеко,но не пришел к уровню Питона. Его место между Си и (условно)Питоном мне непонятно.
Ну да ладно... это не требует ответа - просто ворчание.
ТС! Строчка №13 ... хм... ну а какая же еще могла быть???
выглядит так:
secondType doSomething(){а должна так:
secondType& doSomething(){Понятна ли Вам разница, о мастер Си-Шарпа? ;) ;) ;)
secondType& doSomething(){return *this}Но не понимаю, что происходит в исходном случае. Как я понимаю, должна бы просто выделиться память под экземпляр класса на стеке, которая за ненадобностью тут же будет освобождена. Но, в действительности явно происходит что-то другое. Что-то мне непонятное.
строки 8 и 11? Создаём экземпляр - выбрасываем нахрен, создаём новый, авось лучше будет!
её болезную под элемент списка сперва освобождаем, а потом кидаемся элемент печатать
А вот это непонятно совсем. Как освобождаем память? Где?
Вы, знаете, я вот совершенно искренне хочу Вам помочь, так не заставляйте меня выражаться матом! Я вам сказал поставить печати в Вашем скетче и в двух местах класса. Если Вы это сделаете, то увидите, что память освобождается ДО того, как Вы её печатаете. Тогда Вы поставите печати через строчку и увидите в какой именно строке она освобождается. Вот тогда Вы поймёте что не так. Причём реально поймёте и больше не будете повторять. Так сделайте это, чёрта возьми!
Поймите, я называю помощью именно это. Написать вместо Вас правильно и дать готовый кусок кода - оно, конечно, бытстре и проще, но я это помощью не считаю.
Буду дома - будет распечатка. Просто пока стараюсь докопаться до сути доступными мне средствами, а в данный момент в их число среда разработки на С++ не входит.
То, что она освобождается - это как бы очевидно, ибо печатается NULL. Могу даже с высокой долей вероятности предположить, что освобождается она при возврате из функции. Непонятно мне почему так происходит.
Итак, вот листинг с выводом трассы:
#include<LinkedList.h> class firstType{ public: firstType(){Serial.println("create first");} ~firstType(){Serial.println("delete first");} }; class secondType{ public: LinkedList<firstType*> list; secondType(){Serial.println("create second");} ~secondType(){Serial.println("delete second");} secondType doSomething(){ Serial.println("enter doSomething"); list.add(new firstType()); Serial.println("exit doSomething"); return *this; } }; void setup() { Serial.println("enter setup"); Serial.begin(9600); secondType v; v.doSomething(); Serial.println(v.list.size()); Serial.println(v.list.get(0)==NULL); Serial.println("exit setup"); } void loop() {}И вот что на выходе:
create second
enter doSomething
create first
exit doSomething
delete second
1
1
exit setup
delete second
Признаюсь, понятнее не стало...
Ну, во-первых, Вы не поставили печать в конструкторе и деструкторе класса LinkedList, как я Вам советовал, а потому Вы не видите сколько экземляров класса Вы создаёте и когда их уничтожаете.
Найдите в библиотеке конструктор и деструктор и замените их хоть на такие
template<typename T> LinkedList<T>::LinkedList() { Serial.println( "LinkedList::LinkedList"); root=NULL; last=NULL; _size=0; lastNodeGot = root; lastIndexGot = 0; isCached = false; } // Clear Nodes and free Memory template<typename T> LinkedList<T>::~LinkedList() { Serial.println( "LinkedList::~LinkedList"); ListNode<T>* tmp; while(root!=NULL) { tmp=root; root=root->next; Serial.print( "delete "); Serial.println((unsigned)tmp); delete tmp; } last = NULL; _size=0; isCached = false; }Кроме того, советую перед строкой 34 скетча поставить delete v; и увидеть, что память, которую Вы запрашиваете в строке 19, никогда и никем не освобождается. Но, это нам с Вами навырост, когда с первыми поблемами разберёмся.
Сделано
#include<LinkedList.h> class firstType{ public: firstType(){Serial.println("create first");} ~firstType(){Serial.println("delete first");} }; class secondType{ public: LinkedList<firstType*> list; secondType(){Serial.println("create second");} ~secondType(){Serial.println("delete second");} secondType doSomething(){ Serial.println("enter doSomething"); list.add(new firstType()); Serial.println("exit doSomething"); return *this; } }; void setup() { Serial.begin(9600); Serial.println("enter setup"); secondType v; v.doSomething(); Serial.println("after doSomething"); Serial.println(v.list.size()); Serial.println(v.list.get(0)==NULL); delete &v; Serial.println("exit setup"); } void loop() {}inkedList<T>::LinkedList() { Serial.println( "LinkedList::LinkedList"); root=NULL; last=NULL; _size=0; lastNodeGot = root; lastIndexGot = 0; isCached = false; } // Clear Nodes and free Memory template<typename T> LinkedList<T>::~LinkedList() { Serial.println( "LinkedList::~LinkedList"); ListNode<T>* tmp; while(root!=NULL) { tmp=root; root=root->next; delete tmp; } last = NULL; _size=0; isCached = false; }Вывод:
enter setup
LinkedList::LinkedList
create second
enter doSomething
create first
exit doSomething
delete second
LinkedList::~LinkedList
after doSomething
1
1
delete second
LinkedList::~LinkedList
exit setup
delete second
LinkedList::~LinkedList
А почему Вы не вставили 25 строку из моего текста из предыдущего поста? Она как раз показывает когда класс освобождает элемент списка. Вставьте! Увидите, что память у Вас освобождается до Вашей попытки её читать.
А пока посчитайте сколько раз Вы создаёте экземпляр класса secondType и сколько раз его освобождаете. Также посчитайте и для firstType.
Вас не смущает, что LinkedList Вы уничтожаете больше раз, чем создаёте? А firstType - наоборот, создаёте, а уничтожать дядя будет?
Виноват, не заметил.
Да, косяк с утечкой памяти я уже понял. Полагаю, delete &list в деструкторе secondType должно решить проблему?
Но мы ещё не приблизились к ответу на основной вопрос в теме: что происходит в sometype sometype::foo(){return *this;}
upd
Проверил, delete &list проблему не решает.
решает for(int i = 0;i<list.size();i++)delete list.get(i); создавать список указателей с этой библиотекой тупая мысль.
Полагаю, delete &list в деструкторе secondType должно решить проблему?
Нет. Хотя, попробуйте, чтобы убедиться самому. Кстати, странно, что Вы начали тут писать что именно Вы полагаете, не попробовав.
Но мы ещё не приблизились к ответу на основной вопрос в теме: что происходит в sometype sometype::foo(){return *this;}
тут сложно, у Вас накручено одна ошибка на другой сидит. И Очень трудно отделить мух от котлет, т.к. они интерферируют.
Кроме того, Вы упорно игнорируете мою просьбу поставить печать в том месте, где элемент списка освобождается (возле delete) - строка 25 в моём посте #18
Сделайте это и скажите мне (глядя на свою выдачу) в какой именно момент происходит освобождение памяти элемента списка?
Кому попкорн? ;)
Мне вмешаться со своим "французским"? Я уже в нужной кондиции.... грамм 600 самогончика уже скушал... могу объяснить тонкости С++ на народном языке. Если что...
-----------------------
Посчитайте, есть ли живой линкед-лист к моменту печати. Как уничтожался ненужный экземпляр секонда из стека при ретурне? Последовательно, по шагам.
Сделайте это сами, а то ведь я могу не дождаться разрешения от Евгения ;) !
=============================
И внемите моему совету - это не простые моменты ООП. Не нужны они Вам совсем.
Сделайте это и скажите мне (глядя на свою выдачу) в какой именно момент происходит освобождение памяти элемента списка?
Сделано. Вот скетч
#include<LinkedList.h> class firstType{ public: firstType(){Serial.println("create first");} ~firstType(){Serial.println("delete first");} }; class secondType{ public: LinkedList<firstType*> list; secondType(){Serial.println("create second");} ~secondType(){for(int i = 0;i<list.size();i++)delete list.get(i); Serial.println("delete second");} secondType doSomething(){ Serial.println("enter doSomething"); list.add(new firstType()); Serial.println("exit doSomething"); return *this; } }; void setup() { Serial.begin(9600); Serial.println("enter setup"); secondType v; v.doSomething(); Serial.println("after doSomething"); Serial.println(v.list.size()); Serial.println(v.list.get(0)==NULL); delete &v; Serial.println("exit setup"); } void loop() {}и выдача
enter setup
LinkedList::LinkedList
create second
enter doSomething
create first
exit doSomething
delete first
delete second
LinkedList::~LinkedList
delete list item
after doSomething
1
1
delete second
LinkedList::~LinkedList
delete list item
exit setup
delete second
LinkedList::~LinkedList
Посчитайте, есть ли живой линкед-лист к моменту печати.
Как уничтожался ненужный экземпляр секонда из стека при ретурне?
ответ в вашей выдачи после exitDoSomething и печатью. Дошло??????
Женя, жду разрешения... обещаю почти не пользоваться матом.
ответ в вашей выдачи после exitDoSomething и печатью. Дошло??????
Кстати, теперь я ещё не понимаю почему list.size() удалённого списка возвращает 1
это не простые моменты ООП
И к этой непростоте добавляется ещё и говнокод, гордо называемый библиотекой, который умудряется использовать класс ListNode без виртуального деструктора и без вызлва и, стало быть, без возможность освободить память элемента списка.
Блин, ну я с Вас балдею. У меня же в той печати стоял вывод адреса temp! нафига было удалять? Он бы нам пригодился.
Теперь видете, что list item удаляется до того, как Вы пытаетесь до него добраться и напечатать? Вилите или нет?
Женя, жду разрешения... обещаю почти не пользоваться матом.
Да, уж и я скоро начну. Четрые поста не можем вставить печать, которую я готовую дал.
Блин, ну я с Вас балдею. У меня же в той печати стоял вывод адреса temp! нафига было удалять? Он бы нам пригодился.
Действительно, виноват. Но, если я правильно понял, то вся беда в том, что в деструкторе LinkedList вместо delete *firstClass вызывается delete **firstClass. Если это так, то давайте закроем тему с утечкой памяти. Если нет, то можете матом, я не обижусь.
Теперь видете, что list item удаляется до того, как Вы пытаетесь до него добраться и напечатать? Вилите или нет?
Действительно, виноват. Но, если я правильно понял, то вся беда в том, что в деструкторе LinkedList вместо delete *firstClass вызывается delete **firstClass. Если это так, то давайте закроем тему с утечкой памяти. Если нет, то можете матом, я не обижусь.
Нет, всё там не так. Это класс вообще не предназначен для хранения указателей. При любой операции удаления элемента списка (а их там много) он удаляет только свою структуру "элемент списка", а Ваш класс, на который ссылается этот элемент, удалять некому - он никогда не удаляется.
Но это другая проблема, давайте с Вашим *this разбираться.
Вы по прежденму не понимаете что происходит? Мне объяснять построчно?
Но это другая проблема, давайте с Вашим *this разбираться.
Вы по прежденму не понимаете что происходит? Мне объяснять построчно?
Вей' з мир!
========================
попробуйте посмотреть разницу в варианте, что давал давно со ссылкой и вашем, который использован в последней выдаче.
Сделайте такую же распечатку, только со ссылкой в описании функции ду-самсинг. Это была подсказка.
Вей' з мир!
========================
попробуйте посмотреть разницу в варианте, что давал давно со ссылкой и вашем, который использован в последней выдаче.
Сделайте такую же распечатку, только со ссылкой в описании функции ду-самсинг. Это была подсказка.
в стеке создается КОПИЯ нашего единственного секонд. КОПИЯ!!! Но данные то в ней такие же. И, при уничтожении копии, уничтожаются все элементы, а они ровно теже самые(!!!), что и в оригинале. Поэтому к-во элементов в распечатке - 1, а содержимое NULL. Теперь дошло? К-во элементов в ОРИГИНАЛЕ, никто не менял, поэтому оно осталось 1, а сами объекты - удалены.
==================
А при ссылке, как и положено, деструктор не вызывается.
----------------------
Еще раз - НЕ НАДО ВАМ ИСПОЛЬЗОВАТЬ ТАКИЕ КОНСТРУКЦИИ.
Тогда, смотрим на вот этот код. В него я вставил печать контрольных точек через строчку, чтобы было понятно, что и когда происходит. Я Вам писал про это Выше, но Вы не захотели их вставлять, а зря. Вот код
template <typename T> inline Print & operator << (Print &s, T n) { s.print(n); return s; } #define INVESTIGATION_PRINT #include <LinkedList.h> class firstType { public: firstType(void) { Serial << "firstType::firstType\n"; } ~firstType(void) { Serial << "firstType::~firstType\n"; } }; class secondType { public: LinkedList<firstType*> list; secondType() { Serial << "Point #2\n"; list = LinkedList<firstType*>(); Serial << "Point #3\n"; } secondType doSomething() { Serial << "Point #5\n"; firstType* tmp = new firstType(); Serial << "Point #6\n"; Serial << "Got memory: " << ((unsigned)tmp) << '\n'; list.add(tmp); Serial << "Point #7\n"; return *this; } }; void setup() { Serial.begin(115200); Serial << "Point #1\n"; secondType* v = new secondType(); Serial << "Point #4\n"; v->doSomething(); Serial << "Point #8\n"; Serial << "v->list.size()=" << v->list.size() << '\n'; Serial << "(v->list.get(0) == NULL) =" << (v->list.get(0) == NULL) << '\n'; delete v; Serial << "Point #9\n"; } void loop() {}И вот, что он печатает
Постарайтесь расположить код и выдачу рядом (на бымаге на разных листах напечатайте и положите рядом) и прямо пальце ведите построкам и читайте посянения.
Поехали.
После Point#1, при срабатывании строки 37 кода, ещё до того, как начнёт исполняться конструктор (Point#2) создаётся объект LinkedList (выдача - строка 2). Почему он создаётся? Потому что Вы описали его как свойство класса (именно его, а не указатель), а ствойства всегда иниализируются ДО вызова конструктора.
В строке 19 кода (между point№2 и point№3) Вы создаёте новый экземпляр LinkedList (вызов конструктора в строке №4 выдачи) и присваиваете его свойтсву list. При этом ранее созданный экземпляр удаляется (вызов деструктора в строке №5 выдачи).
Дальше всё более или менее предсказуемо, до Point#7. Здесь Вы собираетесь возвращать *this
При этом происходит следующее.
1) создаётся новый объект, который собственно и будет возвращаться.
2) при этом в новый объект копируется содержимое текущего.
И вот тут самая тонкость. Даже две.
Для него не вызывается конструктор. Просто выделяется память и копируется содержиоме объекта. В прнципе, если бы у Вас был специальный "конструктор копирования", он был бы вызван, но его нет. Потому, создание копии происходит без вызова конструктора - простым копированим области памяти.
Вторая тонкость - текущий объет содержит указатели. Много указателей. И этти указатели копируется в новый объект. Но при этом не дублируется то, на что указатели указывают! Только копируются указатели. В результате, мы имеем два объекта (this и вновь созданный для возвращения значения) в которых есть одианковые указатели, указывающие на одни и те же области памяти! Это понятно?
Едем дальше.
Строка 14 выдачи показывает, что недавно созданный для возвращения объект удаляется (вызывается деструктор). Удаляется он потому. что создан был исключительно для возвращения значения функции, но этого значения никто не ждёт - оно никому нафиг не нужно.
При уделнии, он чистит свой список айтемов и прописывает везде нули.
Тут ещё одна тонкость. Деструктор прописывает нули по своим указателям. Но ведь указатели теперь у нас указывают на те же самые область памяти, на которые указывают указатели другого экземпляра! Так вышло при копировании! Т.е., освободив память элемента списка, мы обидели наш старый добрый объект - теперь его куазатель элемента списка указывает на освобожденную память, указатель "на первый" смотрит на 0 и т.п.
А вот size не поменялся, т.к. это просто переменная. Она была скопирована при копировании объекта, но она у каждого объекта своя - уникальная.
Так понятно?
Теперь "что делать?".
Или не использовать (явно или неявно) присваивание объектов ListItem друг другу или доопределить конструктор копирования (или на худой конец опероатор присваивания).
-----------------
Тпереь про вторую ошибку. Этот класс не заточен под то, что его элементами будут указатели. Так как Вы определили - не работает, память не освобождается. делайте список объектов, а не указателей.
Строка 14 выдачи показывает, что недавно созданный для возвращения объект удаляется (вызывается деструктор). Удаляется он потому. что создан был исключительно для возвращения значения функции, но этого значения никто не ждёт - оно никому нафиг не нужно.
При уделнии, он чистит свой список айтемов и прописывает везде нули.
И вдогонку вопрос: не подскажите ли адекватную библиотеку работы со списками?
Нет, не подскажу. На больших машинах, там всё по-другому, а здесь я вообще универсальными вещами не пользуюсь - дорого.
Да это вполне адекватна, только не используйте указатели как элементы списка.
Кстати, вот ещё что подумал: мне кажется, что отсутствие вызова деструктора элемента в списке - это не баг, а вполне себе архитектурное решение. Ибо не всегда надо уничтожать элемент при удалении контейнера.
Как-то так.
Конечно не баг. Элемент вовсе не обязан быть указателем на экземпляр класса. Например, он имеет право быть числом, и какой ему нафиг деструктор.
Класс NodeItem должен иметь виртуальный деструктор - это решит проблему. Надо, переопределяй и чисть, не надо - забей и ничего не делай.