|
Вопрос # 989/ вопрос открыт / |
|
Здравствуйте, эксперты!
Как-то хотел использовать свой написаный класс TRecList и столкнулся с одной проблемой.
Как известно мой класс работает следующей структурой:
TRec = record
Sled: Pointer;
Pred: Pointer;
//user type (write by class)
Buf: array of byte (FRecSize);
end;
Таким образом работать с классом удобно, только если пользовательские данные определены на этапе компияции, но вот с динамическими данными возникает проблема. В частности проблема касается типа string. Решением может служить замена типа string на PChar, но это очень не удобно. Поэтому, хочу спросить можно ли вообще хранить в записях данимические сроки и как?
Не так давно мне попал в руки компонент TVirtualStringTree, там динамические строки храняться премерно таким же образом, как и в моем классе, но ошибки не возникают. Возможно потому, что там используется свой диспечер памяти.
К вопросу прикреплён файл. Загрузить » (срок хранения: 60 дней с момента отправки вопроса)
 |
Вопрос задал: SMaks (статус: 1-ый класс)
Вопрос отправлен: 19 октября 2007, 16:36
Состояние вопроса: открыт, ответов: 0.
|
Мини-форум вопроса
Всего сообщений: 7; последнее сообщение — 21 октября 2007, 21:22; участников в обсуждении: 2.
|
Вадим К (статус: Академик), 19 октября 2007, 17:47 [#1]:
одну ошибку я нашёл сразу
для этого нужно в папке Test_2 в файле UnMain.pas сделать исправление в строке 123. она должна выглядеть так
SetLength(Rec.Fio, 20);
а выглядела она
SetLength(Rec.Name, 20);
в результате память под строку не выделялась и происходил глюк.
после этого исправления кнопка Gen заработала
дальше. в коде я нашёл такую конструкцию
System.Move(Cur^, Rec, FRecSize);
с "динамическими" строками это работать не будет. дело в том, что перменная на самом деле хранит указатель на структуру в памяти, которая хранит строку.
и поэтому сохранить структуру с динамическими типами так просто нельзя.
что же делать.
я в таких случаях использую стандартный класс TList, для которого пишеться обёртка (не наследование, а делегирование). а каждый класс самостоятельно умеет сохранять себя в поток и делать копию. и всё, никаких проблем
а ваш класс показался мне слишком запутаным.
Галочка "подтверждения прочтения" - вселенское зло.
|
|
SMaks (статус: 1-ый класс), 20 октября 2007, 01:46 [#2]:
Работат с моим классом мне кажется удобнее и скорость его работы достаточно высока. Поэтому отказываться от него и использовать классы производные от TList не хочется. В приципе работа с динамическими строками логична, попросто теряется указатель на саму строку в памяти, т.к. она уже не существует. Поэтому вопрос, как можно увеличить счетчик ссылок программно у строки? И можно ли определить что по данному адресу храниться именно строка?
Мне известно что этот самый счетчик граниться в конце строки за #0, но вот изменить мне его (если я что-то не спутал) что-то не удалось.
|
|
SMaks (статус: 1-ый класс), 20 октября 2007, 02:31 [#3]:
Посмотрев как реализуется выделение диспечером памяти в TVirtualStringTree, я понял что допустил одну маленькую ошибку, даже не ошибку, а недочет:
var
p: TCurRec;
...
p := RecList.Add;
RecList.FillCharItem(p, #0); //После этого все работает лучше некуда. Замечу, что это надобыло еще делать (очищать память) в самом классе.
Как время будет, доделаю класс и выложу очередную версию на сайт.
---------------------------
Кстати, вопросы про изменение счетчика ссылок и определение, является ли данными строка, еще актуальны.
|
|
Вадим К (статус: Академик), 20 октября 2007, 15:37 [#4]:
С моей точки зрения, ваш класс может быстро реализовывать вставку. а вот доступ к произвольному элементу - только прямым перебором.
Наследоваться от TList - это плохо, а лучше и правильней делегировать его. Это немного разные вещи.
Цитата:
Мне известно что этот самый счетчик граниться в конце строки за #0, но вот изменить мне его (если я что-то не спутал) что-то не удалось.
Нет, он там не храниться. хотя это можно и логически додуматься - строка ведь может постоянно увеличиваться/уменьшаться...
на самам деле всё храниться просто. сама переменная типа string хранит адрес области памяти, где располагается строка. на 4 предыдущих байтах (тоесть с отрицательным смещением) храниться размер строки. еще 4 байта назад (смещение -8) храниться счётчик ссылок. ведь когда вы присваиваете одну строку другой, то делфи не вставляет код копирования строки, а просто увеличивает счётчик. а когда одна с строк будет модифицирована, то произойдёт копирование строки, уменьшение счётчика. Для констант счётчик равен -1. Это также означает, что сама строка может находиться в области, доступной только для чтения и модификация строки невозможна.
Также, у меня есть сведенья, что по адресу -12 ещё что то храниться. но я не знаю что именно, появилось начиная толи с 7, толи 6 делфи. Но я не рекомендую ручками редактировать эту структуру. Так как обманывая компилятор, можно натворить таких чудес...
В вашем посторении идиологии работы есть одна маленькая, но очень огромная ошибка. Вы делаете так называемую "побитную копию". Для POD типов это работает. В остальных случаях - нет. Строки, объекты не являются POD типами. Как минимальный способ заставить ваш код работать правильно, я вижу такое. Кроме объявления структуры, нужно также объявить процедуру копирования. тоесть "конструктор копий". Если сделать всё хорошо, то этот конструктор доолжен быть делегатом класса. Если его не присвоили, то класс вызвет побитовый конструктор.
Галочка "подтверждения прочтения" - вселенское зло.
|
|
SMaks (статус: 1-ый класс), 21 октября 2007, 08:53 [#5]:
Спасибо, я уже подумал об объявлении процедуры сохранения и загрузки в поток, т.к. для не POD типов просто копирование структуры является не верным способом. Насчет делигированного "конструктора копий", я подумаю как его лучше сделать. Еще сделаю возможность не обязательного хранения данных в памяти (делигированную функцию GetData), это позволит работать с БД (и не только).
--------------------
> С моей точки зрения, ваш класс может быстро реализовывать вставку. а вот доступ к произвольному элементу - только прямым перебором.
Первое обращение к произвольному элементу по индексу медленное, но далее скорость обращение к этому же элементу возрастает. И вероятность, что к этому же элементу в короткое время обратятся намного выше. Поэтому производительность от этого не страдает.
В одном из проектов, где приходилось работать с большим списками, обращение по индексам работало очень долго. Однако, когда я придумал буферизацию элемента, к которому было последнее обращение, то скорость работы со списком возросла в сотни раз.
Поэтому в данно классе (TRecList) я ввел возможность буферизовывать произвольное количество элементов списка.
Но чаще всего лучше прямо обращаться к элементу по адресу.
|
|
Вадим К (статус: Академик), 21 октября 2007, 13:47 [#6]:
Цитата:
И вероятность, что к этому же элементу в короткое время обратятся намного выше.
Очень смелое утверждение. есть разные операции. недаром в с++ реализованны классы vector, который даёт быстрый доступ к произвольному элементу или list, который даёт быструю вставку в произвольное место.
Например, если мы производим суммирование, то надо последовательно пробежать по элементам, но здесь у вашего класса есть соответствующий метод. Но часто нужно произвольный доступ.
Также к недостаткам отнесу большие накладные расходы. в класса TList - это 4 байта на каждый элемент, а у вас 12(если не ошибаюсь)
Также замечу, что часто важна не скорость, а гарантия того, что доступ к элементу будет осуществленна за константное время или за "гарантированное время". В системах, где время критично, это очень важно.
Но есть ещё одна пакость, которая запрятана в вашем классе и которую вы не видите. ваш класс при последовательном проходе по элементам после продолжительной работы (добавления/удаления) будет значительно подтормаживать. почему? а дело в том, что бы пройтись по всем элементам вы используете ссылку на следующий элемент. но в памяти они физически могут находиться в самых разных местах. кэш процессора будет постоянно сбрасываться, а это приведёт к потере производительности 3-5, а то и больше раз.
Цитата:
Но чаще всего лучше прямо обращаться к элементу по адресу.
а вот это заблуждение. ведь запомнив в одном месте адрес элемента, в другом можно удалить нечаяно его. и искать такую ошибку будете очень долго.
Галочка "подтверждения прочтения" - вселенское зло.
|
|
SMaks (статус: 1-ый класс), 21 октября 2007, 21:22 [#7]:
> Также к недостаткам отнесу большие накладные расходы. в класса TList - это 4 байта на каждый элемент, а у вас 12(если не ошибаюсь)
Пока 8 байт.
> Но в памяти они физически могут находиться в самых разных местах. кэш процессора будет постоянно сбрасываться, а это приведёт к потере производительности 3-5, а то и больше раз.
Часть списка (TList) тоже может в находиться в файле подкачке и примерно так же будет потеря производительности. К тому же в большом списке (TList) при раширение будет уходить много времени на его копирование, в моем классе этого нету.
Естественно, я не призываю только использовать мой класс или какой-либо другой, лучше использовать любой класс по назначению.
> а вот это заблуждение. ведь запомнив в одном месте адрес элемента, в другом можно удалить нечаяно его. и искать такую ошибку будете очень долго.
Это вопрос акуратности программирования. И при нормальном удалении элемента через класс, можно всегда его при необходимости проверить на вилидатность.
|
Чтобы оставлять сообщения в мини-форумах, Вы должны авторизироваться на сайте.
|