
![]() |
Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
|
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[216.73.216.3] |
![]() |
|
Сообщ.
#1
,
|
|
|
Детский вопрос: имеется объект - наследник TObject
![]() ![]() TMap = class public Name: string; GeodeticLimits: TLimits; //геодезические координаты в радианах PlaneLimits: TLimits; //прямоугольные координаты NumberOfObjects: integer; //количество объектов карты Units: byte; //единицы измерения ListOfObjects: TObjectList; //список объектов карты ErrorMsg: string; //строка диагностики Loaded: boolean; //загружена или нет constructor Create; end; набор карт хранится в глобальной переменной BigMap, которая имеет класс TObjectList var BigMap: TObjectList; при создании OwnsObjects указывается как True ![]() ![]() if BigMap = nil then begin BigMap:= TObjectList.Create(True); BigMap.Add(Map); end else BigMap.Add(Map); при необходимости удаления ![]() ![]() var OnThere, Find: boolean; CurrentMap: TMap; ............. begin .................. I:= 0; Find:= False; //проверка на попадание точки ВС в карту из списка загруженных repeat CurrentMap:= TMap(BigMap.Items[I]) ; OnThere:= IamThere(MyPlace, CurrentMap.PlaneLimits); if IamThere(MyPlace, CurrentMap.PlaneLimits)=False then begin BigMap.Delete(I); //удаляем, раз ВС не на карте BigMap.Pack; //упаковка BigMap.Capacity:= BigMap.Count; //выравнивание по реальному счетчику Find:= True; end else inc(I); until (Find = True) or (I = BigMap.Count); ......... end; все делает, но память не освобождает. В чем может быть проблема? |
Сообщ.
#2
,
|
|
|
>все делает, но память не освобождает.
Деструктор TMap вызывается? Перекрытия Destroy я не вижу, а кто будет ListOfObjects оcвобождать? |
Сообщ.
#3
,
|
|
|
А разве он не вызывается автоматически?
|
Сообщ.
#4
,
|
|
|
Вызывается деструктор TObject, который освобождает память, записанную в InstanceSize, финализирует динамические поля. Но про внутренние объекты он ничего не знает.
Нужно объявить и реализовать перекрытый (override) деструктор, в котором освободить то, что создавал вручную (и не забыть inherited) |
Сообщ.
#5
,
|
|
|
Ок, добавил destructor TMap.Destroy; override;
![]() ![]() destructor TMap.Destroy; begin GeodeticLimits.Free; PlaneLimits.Free; ListOfObjects.Clear; ListOfObjects.Free; inherited Destroy; end; Но результат тот же. |
Сообщ.
#6
,
|
|
|
Деструктор вызывается? Проверено?
В чём выражается "но память не освобождает"? ListOfObjects освобождает свои объекты? В общем, тут много неясного, без телепатии тяжко. |
Сообщ.
#7
,
|
|
|
Смотрю в отладчике - ничего не понятно: после удаления (Bimap.Delete(I)) ListOfObjects пустой, но указатель есть, его TList не nil, но пустой. В диспетчере задач видно, что сколько занято памяти, столько и осталось занятым после удаления...
|
Сообщ.
#8
,
|
|
|
> ListOfObjects пустой, но указатель есть
А он и не должен обнуляться. Есть процедура FreeAndNil, которой пользуются при необходимости иметь нулевую ссылку после уничтожения (замечу, что на объект может быть несколько ссылок, а обнулится только одна) Диспетчер задач - не всегда показатель. |
Сообщ.
#9
,
|
|
|
Цитата MBo @ > ListOfObjects пустой, но указатель есть Диспетчер задач - не всегда показатель. А как еще отслеживать память? Из-за утечек получаю outofmemory, причем, довольно скоро, потому что загружаемые и не удаленные карты по 100-200 Мб. По идее, даже если создается простейший наследник TObject, его деструктор надо перекрывать для корректного освобождения ресурсов. И далее по иерархии своих классов. Я правильно понимаю? |
Сообщ.
#10
,
|
|
|
>А как еще отслеживать память?
Включить ReportMemoryLeaksOnShutdown http://www.gunsmoker.ru/2009/05/blog-post_24.html >По идее, даже если создается простейший наследник TObject, его деструктор надо перекрывать для корректного освобождения ресурсов. Если есть внутренние объекты, созданные в твоём коде - да, иначе нет. О статических полях и данных с автоматическим управлением (строки, динамические массивы, если они не содержат объекты) заботиться не нужно. |
Сообщ.
#11
,
|
|
|
Спасибо, постараюсь разобраться
|
Сообщ.
#12
,
|
|
|
Цитата mnj @ все делает, но память не освобождает. В чем может быть проблема? У тебя код удаления карт в #1 какой-то странный - он удаляет из списка не все карты, на которые не попадает заданная точка, а только одну, первую попавшуюся (выход из цикла по первому Find = true) Цитата mnj @ По идее, даже если создается простейший наследник TObject, его деструктор надо перекрывать для корректного освобождения ресурсов. В деструкторе нужно удалять\освобождать объекты (экземпляры классов), а также динамические структуры данных (указатели), выделенные функциями New, GetMem, AllocMem, ReallocMem (или функциями WinApi). Как уже было сказано, автоматически очищаются только строки, динамические массивы (не содержащие объектов и указателей), варианты и интерфейсы. Цитата mnj @ постараюсь разобраться В первую очередь обрати внимание на класс объекта карты (то, что хранится в ListOfObjects), поскольку эти объекты, по идее, и занимают основной объем карты в 100-200 Мб, о которых ты говоришь. Поэтому, если деструктор этого объекта что-то не удаляет, то отсюда и возникает большая утечка памяти. Цитата mnj @ А как еще отслеживать память? Кроме ReportMemoryLeaksOnShutdown можно проверить освобождение памяти в конкретных местах вставкой вызовов функций GetMemoryManagerState или GetHeapStatus (она хоть и deprecated, но проще в использовании, чем GetMemoryManagerState). |
Сообщ.
#13
,
|
|
|
Прежде всего - спасибо за внимание к моей проблеме. Теперь по порядку:
Цитата leo @ У тебя код удаления карт в #1 какой-то странный - он удаляет из списка не все карты, на которые не попадает заданная точка, а только одну, первую попавшуюся (выход из цикла по первому Find = true) Все карты и не грузятся. Грузятся только те карты, на которые попадает точка в стартовой позиции, их м.б. 1, 2 или 3 штуки (я брал готовые, а они внахлест сделаны, с перекрытием друг друга, не как нормальные планшеты), поэтому по мере продвижения точка может покинуть только 1 карту. Про деструктор: Вот у меня создан простейший класс, от которого ползет все: ![]() ![]() TMyPoint = class Lon: real; //долгота Lat: real; //широта //destructor Destroy; override; end; Нужен ли ему свой конструктор и свой деструктор, если он прямой наследник TObject и отличается только наличием двух статических полей? В отладчике видно, что он создается без собственного конструктора и без проблем со всеми своими полями, а вот удалить его с деструктором или без него не получается, в отладчике он по-прежнему жив-здоров, если удалять экземпляр проcтым Free, а не FreeAndNil. Получается, что деструктор ему не нужен вовсе. ![]() ![]() destructor TMyPoint.Destroy; begin inherited Destroy; end; Что еще можно написать для него, если деструктор у предка - пустой. А вот его родственник: ![]() ![]() //треугольник TTriangle = class Vertexes: array [0..2] of TMyPoint; constructor Create; destructor Destroy; override; end; .... constructor TTriangle.Create; var I: integer; begin for I := 0 to 2 do Self.Vertexes[I]:= TMyPoint.Create; end; destructor TTriangle.Destroy; var I: integer; begin for I := 0 to 2 do begin if Assigned (Self.Vertexes[I]) then Self.Vertexes[I].Free; end; inherited; end; Тем не менее FastMM4 выдает такую штуку: 5CE4D6 [Types_classes.pas][Types_classes][TTriangle.$bctr$qqrv][389] 5D0B78 [main.pas][main][Triangulation$qqrp20System.Classes.TList][590] 5D0CF7 [main.pas][main][CreatePlane$qqrv][634] 5D11D1 [main.pas][main][TGLForm.FormCreate$qqrp14System.TObject][773] The block is currently used for an object of class: TTriangle От конструктора формы к процедуре рисования контура самолета (CreatePlane), потом к триангуляции и к треугольнику. Строка проблемного элемента - конструктор этого самого треугольника. Или 5CDC92 [Types_classes.pas][Types_classes][TLimits.$bctr$qqrv][212] 5CE258 [Types_classes.pas][Types_classes][TMyFrame.$bctr$qqrv][349] 5D399A [main.pas][main][TGLForm.BuildFrames$qqrv][1466] 5D11EF [main.pas][main][TGLForm.FormCreate$qqrp14System.TObject][776] The block is currently used for an object of class: TMyPoint Тут все упирается в TLimits (габаритный контейнер карты), который тоже несложный, вроде и создается и умирает корректно, а TMyPoint, получается, палки в колеса ставит все равно, коли без freeandnil не обойтись: ![]() ![]() TLimits = class //границы листа карты SW: TMyPoint; //юго-западный угол NW: TMyPoint; //северо-западный SE: TMyPoint; //юго-восточный NE: TMyPoint; //северо-восточный CoordSys: boolean; constructor Create; destructor Destroy; override; end; ................ constructor TLimits.Create; begin // inherited Create; SW:= TMyPoint.Create; //юго-запад NW:= TMyPoint.Create; //северо-запад SE:= TMyPoint.Create; //юго-восток NE:= TMyPoint.Create; //северо-восток CoordSys:= True; //по умолчанию - прямоугольные координаты end; destructor TLimits.Destroy; begin if Assigned(SW) then FreeAndNil(SW); if Assigned(NW) then FreeAndNil(NW); if Assigned(SE) then FreeAndNil(SE); if Assigned(NE) then FreeAndNil(NE); inherited; end; Где правда, если с TLimits есть проблема и в чем? ПС - огромная утечка в функции тесселяции полигона при построении объекта карты, который отправляется в ListOfObjects, но это видно только в логе, FastMM говорит Unknown |
Сообщ.
#14
,
|
|
|
> в отладчике он по-прежнему жив-здоров, если удалять экземпляр проcтым Free, а не FreeAndNil.
Что значит - жив-здоров? Указатель без обнуления указывает всё туда же, но его участок памяти помечен как свободный, очищать его ни к чему. >Получается, что деструктор ему не нужен вовсе. Такому классу переписывать деструктор не нужно, как я уже говорил. Более того, и класс здесь не нужен, если не предусматривается наследование-расширение, так как никакой функциональности или инкапсуляции он не реализует. Просто массив или запись. > if Assigned (Self.Vertexes[I]) then Self.Vertexes[I].Free; Проверка здесь ни к чему. Free само разберётся, нулевой указатель освобождать не станет |
Сообщ.
#15
,
|
|
|
Цитата MBo @ Что значит - жив-здоров? Указатель без обнуления указывает всё туда же, но его участок памяти помечен как свободный, очищать его ни к чему. Жив-здоров - это после выполнения Free можно по-прежнему присвоить значение полю объекта как вполне существующему, значит он продолжает жить и память не освобождает. Но мне нужно убить его совсем. Класс для точки - конечно, жирно. Это я от лени. Правильно было бы ![]() ![]() PPointRecord = ^TPointRecord; //точка TPointRecord = record Lat: real; Lon: real; end; |
Сообщ.
#16
,
|
|
|
>Но мне нужно убить его совсем
Видимо, у тебя в голове сложилась не вполне верная картина работы с памяться, и ты исходишь из неверных предпосылок. Вот менеджер памяти сдавал комнату Васе, потом Вася выехал, а комната всё существует, и даже мебель стоит по-старому. Если спросить, сколько кастрюль на верхней полке, ответ будет тоже, что и при Васе. Но она вакантна, и когда-либо может быть снова сдана - Пете, который уже и мебель переставит, и кастрюли свалит в духовку. |
Сообщ.
#17
,
|
|
|
Цитата MBo @ Видимо, у тебя в голове сложилась не вполне верная картина работы с памяться, и ты исходишь из неверных предпосылок. Вот менеджер памяти сдавал комнату Васе, потом Вася выехал, а комната всё существует, и даже мебель стоит по-старому. Если спросить, сколько кастрюль на верхней полке, ответ будет тоже, что и при Васе. Но она вакантна, и когда-либо может быть снова сдана - Пете, который уже и мебель переставит, и кастрюли свалит в духовку. Вполне возможно. Я полагал, что менеджер памяти, имея поляну (память), для Васи оградил кусочек на ней. Вася уехал, менеджер ограду снял, теперь туда может поселиться Петя, которому может понадобиться кусочек побольше или поменьше и оградку надо ставить соответствующую, иначе Коля может попасть на Петину жилплощадь. А так получается, что удаленный методом Free экземпляр Вася (например, класса который представляет точку) делает свободным (вакантным) свой кусочек памяти и, хотя отладчик по-прежнему показывает последние значения Васиных полей и с ними можно производить манипуляции, любой Коля может придти на это место по усмотрению менеджера памяти и в отладчике для экземпляра Вася рано или поздно будут выводится любые, даже абсурдные значения, в то время как переменная Коля в отладчике будет показывать адекватные значения, если я правильно понял. Как мне убедиться, что память, которую занимал Вася, действительно вакантна? |
Сообщ.
#18
,
|
|
|
Цитата А так получается, что удаленный методом Free экземпляр Вася (например, класса который представляет точку) делает свободным (вакантным) свой кусочек памяти и, хотя отладчик по-прежнему показывает последние значения Васиных полей и с ними можно производить манипуляции, любой Коля может придти на это место по усмотрению менеджера памяти и в отладчике для экземпляра Вася рано или поздно будут выводится любые, даже абсурдные значения, в то время как переменная Коля в отладчике будет показывать адекватные значения, если я правильно понял Всё верно >Как мне убедиться, что память, которую занимал Вася, действительно вакантна? Пройти отладчиком по шагам вызов Free с включенными Use Debug DCUs и убедиться, что деструктор срабатывает. Если утечка и есть, то она вызвана другими причинами |
Сообщ.
#19
,
|
|
|
Цитата mnj @ Как мне убедиться, что память, которую занимал Вася, действительно вакантна? Полноценный FastMM + опции FullDebugMode, CheckHeapForCorruption. Заполняют освобожденные участки шаблоном $80808080. Либо в деструкторе занулять занимаемое объектом место |
Сообщ.
#20
,
|
|
|
Спасибо всем за ценные советы. Но в процессе решения этой задачи (точнее, отработки одного из дырявых мест) вот такая есть утечка. Полноценный FastMM говорит, что она в процедуре триангуляции:
5CB160 [Unit1.pas][Unit1][TfrmGL.Triangulation$qqrv][156] Вот объявлены типы (избавились от классов, делаем рекордами) ![]() ![]() //точка Point = ^P; P = record X: glFloat; Y: glFloat; end; //треугольник PTr = ^T; T = record Vertexes: array[0..2] of P; end; Переменные: ![]() ![]() Points, Triangles: TList; Вот триангуляция: ![]() ![]() procedure TfrmGL.Triangulation; var I: integer; Tr: PTr; //треугольник R: real; //векторное произведение B: boolean; PP: Point; //точка (вершина) begin Triangles:= TList.Create; //список треугольников I:= 0; while Points.Count > 3 do begin if I+2 = Points.Count then I:= 0;//если конец списка, то начинаем сначала New(Tr); //пустой треугольник PP:= Points.Items[I]; Tr.Vertexes[0]:= PP^; //первая вершина PP:= Points.Items[I+1]; Tr.Vertexes[1]:= PP^;//вторая вершина PP:= Points.Items[I+2]; Tr.Vertexes[2]:= PP^;//третья вершина R:= VectorMultiply(Tr.Vertexes[0],Tr.Vertexes[1],Tr.Vertexes[2]); B:= IsPIn_Vector(Tr.Vertexes[0],Tr.Vertexes[1],Tr.Vertexes[2],Points); //если R(векторное произведение AB и AC) < 0 - левая тройка векторов и //остальные точки не попадают в текущий треугольник, то if (R < 0) and (B = False)then begin Triangles.Add(Tr);//добавляем треугольник в список треугольников Dispose(Point(Points.Items[I+1])); Points.Delete(I+1); //удаляем вторую (в треугольнике) вершину из списка, //остальные автоматом сдвигаются вперед к первой end else //иначе проверяем следущий набор из трех вершин if I+2 < Points.Count-1 then inc(I) //если не конец списка минус 2 точки else //переходим на 1 точку дальше if I+2 = Points.Count-1 then I:= 0; //если стоим за 2 точки до конца списка end; //идем сначала end Удаление: ![]() ![]() procedure TfrmGL.FormDestroy(Sender: TObject); var I: integer; begin wglMakeCurrent (0, 0); wglDeleteContext(hrc); for I := 0 to Triangles.Count-1 do Dispose(PTr(Triangles.Items[I])); Triangles.Free; for I := 0 to Points.Count-1 do Dispose(Point(Points.Items[I])); Points.Free; end; Вроде все удаляется, но в репорте 5 штук неопознанных (понятно, что из триангуляции). Где ошибка, не могу найти. Полный код в аттаче. Прикреплённый файл ![]() |
Сообщ.
#21
,
|
|
|
New(Tr); выполняется на каждом шаге, а занесение созданного треугольника в список - не на каждом.
Занесенные освобождаются, а незанесенные - остаются висеть. |
Сообщ.
#22
,
|
|
|
Цитата MBo @ New(Tr); выполняется на каждом шаге, а занесение созданного треугольника в список - не на каждом. Занесенные освобождаются, а незанесенные - остаются висеть. Спасибо! Сам бы не додумался о таких простых вещах ![]() ![]() if (R < 0) and (B = False)then begin Triangles.Add(Tr);//добавляем треугольник в список треугольников Dispose(Point(Points.Items[I+1])); Points.Delete(I+1); //удаляем вторую (в треугольнике) вершину из списка, //остальные автоматом сдвигаются вперед к первой end else //иначе проверяем следующий набор из трех вершин begin if I+2 < Points.Count-1 then inc(I) //если не конец списка минус 2 точки else //переходим на 1 точку дальше if I+2 = Points.Count-1 then I:= 0; //если стоим за 2 точки до конца списка Dispose(Tr); //убираем лишний треугольник и идем сначала end; |
Сообщ.
#23
,
|
|
|
mnj
Зачем раньше времени создавать Tr, если в функции VectorMultiply и IsPIn_Vector можно передать непосредственно точки из Points? К тому же записи (record) давно поддерживают методы, в т.ч. и конструкторы (а для старых версий типа D7 можно вместо record использовать тип object). ![]() ![]() type //треугольник PTr = ^T; T = record Vertexes: array[0..2] of P; constructor Init(const P1,P2,P3: P); end; constructor PTr.Init(const P1,P2,P3: P); begin Vertexes[0]:=P1; Vertexes[1]:=P2; Vertexes[2]:=P3; end; procedure TfrmGL.Triangulation; var ... Tr: PTr; //треугольник P1,P2,P3: Point; //точка (вершина) begin ... P1:= Points.Items[I]; P2:= Points.Items[I+1]; P3:= Points.Items[I+2]; R:= VectorMultiply(P1^,P2^,P3^); B:= IsPIn_Vector(P1^,P2^,P3^,Points); if (R < 0) and (B = False)then begin New(Tr, Init(P1^,P2^,P3^)); //создаем треугольник и инициализируем его вызовом конструктора Triangles.Add(Tr); //добавляем треугольник в список треугольников ... PS: Названиям типов лучше давать нормальные осмысленные имена, начинающиеся с T..., а не просто P и T как у тебя. |
Сообщ.
#24
,
|
|
|
leo
Цитата leo @ Зачем раньше времени создавать Tr, если в функции VectorMultiply и IsPIn_Vector можно передать непосредственно точки из Points? Да, верно. Тогда не будет нужды удалять ненужные треугольники, так как они не будут создаваться. Насчет конструкторов в записях я просто как-то не думал, пользуя от лени класс от tobject. Но если мне, допустим, нужно соорудить габаритный контейнер для листа карты, то удобнее делать класс (я так думаю): ![]() ![]() PMyPoint = ^TMyPoint; TMyPoint = record Longitude: real; Latitude: real; constructor Init(const X,Y: real); end; constructor TMyPoint.Init(const X, Y: real); begin Longitude:= X; Latitude:= Y; end; //а в контейнере угловые точки TBorders = class SW: PMyPoint; NW: PMyPoint; NE: PMyPoint; SE: PMyPoint; constructor Create; destructor Destroy; override; end; |
Сообщ.
#25
,
|
|
|
Цитата mnj @ Но если мне, допустим, нужно соорудить габаритный контейнер для листа карты, то удобнее делать класс (я так думаю): А я так не думаю, поскольку TBorders - это фиксированная структура из 4-х точек, которые не имеет смысла выделять динамически по отдельности. Лучше заменить PMyPoint на TMyPoint, тогда и необходимость в деструкторе отпадет (и соотв-но в использовании класса). По моему для габаритной рамки удобнее использовать вариантную запись типа: ![]() ![]() PMapFrame = ^TMapFrame; TMapFrame = array[0..3] of TMyPoint; PBorders = ^TBorders; TBorders = record case integer of 0: (Frame: TMapFrame); //точки в виде массива 1: (SW, NW, NE, SE: TMyPoint); //отдельные точки с осмыслеными названиями end; В эту запись можно добавить конструктор(ы) для инициализации, а также некоторые методы, например, функцию определения попадания точки в габариты (типа PointInFrame). |
Сообщ.
#26
,
|
|
|
Цитата mnj @ Points, Triangles: TList; В данном случае использовать стандартный TList неудобно, т.к. приходится писать кучу лишнего кода для удаления\освобождения его элементов. Лучше его переопределить, чтобы память под удаляемые элементы освобождалась автоматически. Для этого достаточно переопределить метод TList.Notify ![]() ![]() TFreeMemList = class(TList) protected procedure Notify(Ptr: Pointer; Action: TListNotification); override; end; procedure TFreeMemList.Notify(Ptr: Pointer; Action: TListNotification); begin if (Ptr <> nil) and (Action = lnDeleted) then FreeMem(Ptr); end; |
Сообщ.
#27
,
|
|
|
Вообще, получается, что в моем случае классы, как строительный материал, не нужны, все можно сконструировать записями, тот же объект на карте:
![]() ![]() TMapObject = record //объект на карте Order: byte; //байт для сортировки при записи, означает порядок рисования Class_code: integer; //код классификатора объекта Key: integer; //уникальный номер объекта Local: byte; //код локализации - линейный, площадной, точечный и т.п. Borders: TBorders; //габаритный контейнер NumberOfSubs: integer; //число подобъектов NumberOfPoints: integer; //количество точек NumberOfSem: integer; //количество семантических данных CoordList: array of TMyPoint; //список координат SubObj: array of TMapObject; //список подобъектов Name: ANSIstring; //название или подпись (если есть) Semantic: integer; //семантический ключ для рисования/нерисования Visibility: boolean; //true- рисуется в любом масштабе, false - проверяется на размер constructor Create(AVisibility: boolean); end; Цитата leo @ Такой список можно использовать для любых простых записей, не требующих финализации (т.е. не содержащих строк и т.п.), когда для освобождения элемента можно использовать FreeMem вместо Dispose (=Finalize+FreeMem) А как очистить TList, заполненный такими TMapObject? |
Сообщ.
#28
,
|
|
|
Цитата mnj @ Вообще, получается, что в моем случае классы, как строительный материал, не нужны, все можно сконструировать записями, тот же объект на карте: Не нужно впадать в крайности. Не имеет смысла создавать классы для простых\примитивных структур типа точек, треугольников, габаритов и т.п. А TMapObject - довольно навороченная структура, которая к тому же может иметь (а может и не иметь) подобъекты SubObj аналогичной структуры, которые имеет смысл создавать динамически. Поэтому, на мой взгляд, лучше объявить TMapObject как класс, заменить тип SubObj на TObjectList и удалять его в деструкторе. Цитата mnj @ А как очистить TList, заполненный такими TMapObject? Если записи не простые (содержат строки, динамические массивы и т.п.), то удалять их нужно через Dispose(Тип_указателя_записи(Ptr)); |
Сообщ.
#29
,
|
|
|
Цитата leo @ Не нужно впадать в крайности. Не имеет смысла создавать классы для простых\примитивных структур типа точек, треугольников, габаритов и т.п. Стараюсь ![]() |
Сообщ.
#30
,
|
|
|
Цитата mnj @ Но в тех же габаритах есть необходимость учитывать формат координат (градусы или метры) и производить манипуляции в зависимости от текущего формата. Получается, что нужен все-таки класс... Не знаю. Всё таки TBorders - это не самостоятельная сущность, а просто свойство\характеристика объекта карты или карты в целом. Поэтому учет "DoSomething для градусной нотации и для метрической" может делаться в этих объектах, а не в самом TBorders. Всё зависит от конкретики (в частности от того, что действительно скрывается за DoSomething), иначе можно опять "впасть в крайность" и прийти к выводу, что точка и треугольник тоже должны быть классами, т.к. они также должны учитывать "градусную и метрическую нотацию". |
Сообщ.
#31
,
|
|
|
Цитата leo @ Всё зависит от конкретики (в частности от того, что действительно скрывается за DoSomething), иначе можно опять "впасть в крайность" и прийти к выводу, что точка и треугольник тоже должны быть классами, Пожалуй, остается только согласиться |
Сообщ.
#32
,
|
|
|
Еще раз спасибо всем за помощь и ценные советы.
![]() |