Прошу прощения за тормоза с ответом
Yoda писал(а):
SII писал(а):
- дать доступ к определённым полям экземпляров классов из программ внешнего редактирования (настройка значений свойств объектов не в коде на Це++, а внешними средствами, что, понятное дело, много удобней и быстрей -- создание той же гуйни, расположение неписей на уровне игры и т.д. и т.п.);
Не совсем понятно, в какой степени редактирование является внешним? Редактирование скомпилированных двоичных объектов? Если речь идёт о доступе к полям объектов, то почему не сделать это на уровне класса? Лучше бы рассмотреть на каком-либо конкретном примере, чтобы было понятно, чего именно не хватает.
Ну, в Дельфях сделано, грубо говоря, следующим образом. В состав библиотеки языка, помимо обычной RTL (управление памятью, синусы-косинусы и прочие обычные вещи), входит библиотека визуальных компонентов (VCL). Каждый компонент в ней является классом, содержащим, как и положено, различные поля, свойства и методы. VCL существует сразу в двух видах: как модули, которые подключаются к пользовательской программе (грубо можно считать, что это аналог h-файлов в Си, только в Дельфях всё уже предварительно скомпилировано, хотя в теории можно всю библиотеку собрать с нуля из поставляющихся исходников), и как готовые DLL. Когда идёт разработка гуйни, IDE подгружает DLL, содержащие код используемых компонентов. Когда программист, скажем, добавляет кнопочку, IDE создаёт соответствующий объект, вызывая конструктор нужного класса из DLL. Естественно, этот объект сам себя отрисовывает и т.д. и т.п., поэтому внешне разрабатываемое окно выглядит почти так же, как при выполнении (но кое в чём могут быть отличия, поскольку код объектов учитывает, выполняется ли он в редакторе или в готовом приложении -- в редакторе могут быть доступны некоторые возможности по настройке компонентов и т.д., которые отключены при нормальной работе). Чтобы посмотреть и при необходимости изменить свойства объектов, в IDE существует так называемый инспектор объектов -- это окно, в котором перечислены все опубликованные свойства компонента, с которым мы работаем. Если программист меняет то или иное свойство, IDE сохраняет это изменение в файле формы (файл времени разработки, содержащий информацию обо всех компонентах, используемых в том или ином окне разрабатываемого приложения), а также дёргает сам объект, либо прямо присваивая значение соответствующему полю, либо вызывая нужный метод -- это зависит от того, как определено соответствующее property.
Таким образом, работа с объектом в редакторе по своей сути не отличается от работы с ним в скомпилированной готовой программе -- точно так же происходят обращения к полям или дёрганье методов, разница лишь в некоторых атрибутах объекта (в IDE он создаётся со специальным флагом, указывающим, что идёт работа в редакторе). В Qt и других подобных сишных библиотеках, по всей вероятности, работа идёт принципиально точно так же, но, поскольку Си++ не имеет области видимости published и не имеет свойств, информацию о том, какие свойства у класса доступны для редактирования в IDE, как к ним обращаться и т.п., приходится создавать внешними по отношению к языку инструментами (метаобъектным компилятором для Qt, например), а синтаксически в исходном коде это представляется всякими специальными макросами. Такое решение не шибко элегантно, поскольку "загаживает" исходники всякими костылями и прилично увеличивает и без того немаленькое время сборки проекта. В Дельфях же, поскольку и published, и property являются элементами самого языка, вся необходимая информация в откомпилированные модули помещается самим компилятором в ходе обычной компиляции программы, что куда быстрей и эффективней.
Yoda писал(а):
SII писал(а):
- как бы расширить возможности языка, не заставляя программиста писать каждый раз примерно один и тот же код (например, загрузка и сохранение значений свойств объектов из внешнего файла).
Т.е. что-то типа автоматической сериализации объектов? Если да, то есть два нюанса.
1. Как автоматически сериализировать, если объект содержит аллокируемые области памяти (особенно со ссылками на них и полями, зависящими от размера и размещения аллокированной области)?
2. Если таких полей нет, то почему нельзя трактовать его просто как аморфный двоичный объект в памяти с двумя атрибутами - размещением (получаемым как (void *)&x) и размером (sizeof(x))? Соответственно, копировать, записывать в файл, считывать и т.д.
1. Единственный способ решить эту проблему, на мой взгляд, -- обязать класс иметь специальные методы для сохранения и загрузки, если экземпляры этого класса подлежат сериализации (это может быть атрибутом класса).
2. Не годится из-за того, что не все поля нуждаются в сериализации, а также из-за того, что значения некоторых полей могут зависеть от местоположения объекта в памяти. Первое решается добавлением для поля специального атрибута, а вот второе может быть побеждено только методом-(де)сериализатором .
Цитата:
Само по себе, как мне кажется, property не решает эту задачу, оно только делает удобней определённые средства защиты доступа к полям объекта. Правда, с использованием property могут возникнуть определённые семантические сложности, например, допустимость использования модифицирующих операторов типа ++x, а также неожиданное поведение при реализации доступа по чтению. Например, функция доступа по чтению может скрыто модифицировать значение поля, в результате мы можем получить два разных значения при двух считываниях подряд. Но если во втором случае могут быть даже определённые плюсы (например, мы можем реализовать программный ГСЧ и каждый раз считывать новое случайное число), то организация применимости модифицирующих операторов может существенно усложнить синтаксис языка и затруднить его понимание. Вызов функций-членов в этом смысле лишён каких-либо скрытых эффектов и не позволяет явным образом использовать модифицирующие операторы.
Ну, в Паскале проблем с ++ нет за их отсутствием
Но на самом деле, это вполне прописывается в правила языка: ++ выполняется всегда путём чтения-модификации-записи, причём для чтения и записи либо происходит прямое обращение к полю, либо вызов метода в зависимости от того, как определено свойство. Например, если есть такое определение:
Код:
Internal_Value : Integer;
procedure Set_Internal_Value(New_Value : Integer);
property Value : Integer read Internal_Value write Set_Internal_Value;
то оператор
Код:
Value++;
будет выполнен как:
Код:
Set_Internal_Value(Internal_Value + 1);
Yoda писал(а):
Я хочу сказать, что использование property не так однозначно хорошо, как может показаться сначала, и требует более детального исследования. Вот что действительно однозначно полезно - это разрешение доступа к некоторым приватным полям по чтению. Сеттеры также видятся полезными, т.к. позволяют осуществлять единообразный упрощённый контроль присваемого значения, а также вводить разные степени проверки при разных режимах компиляции (например, проверка допустимости в отладочном режиме). Но и сеттеры тоже требуют отдельного разграничения доступа, к примеру, переменная может быть приватной с публичным сеттером, либо также с приватным сеттером. В общем, не всё так просто.
Во-первых, наличие свойств прилично расчищает исходный код программы: обращение к свойству как к полю элегантней, чем вызов метода (меньше лишних элементов вроде скобок и т.д.), а читабельность -- вещь очень важная, если создаётся не программа-однодневка.
Во-вторых, свойства тоже имеют области видимости. Объяви всё свойство в приватной части -- и его увидят только те, кто имеет на то право (другое дело, что приватные свойства не столь полезны, как имеющие более высокий уровень видимости: в конце концов, на приватном уровне работают те, кто "по долгу службы" знаком с внутренним устройством класса, а соответственно, может прямо модифицировать его поля и т.д. и т.п.).
Yoda писал(а):
SII писал(а):
- наличию области видимости published (тот же public, но известный не только внутри самой программы, но и за её пределами; все свойства, которые можно редактировать в дельфозной ИДЕ, являются именно опубликованными).
Собственно, все члены, объявленные как public, также доступны в любом месте за пределами программы, достаточно лишь корректно узнать тип объекта. В этом смысле я не вижу принципиальных отличий published. Более того (поправьте меня, если я ошибаюсь), published в Delphi как раз и отличается от public тем, что генерирует соответствующую информацию о типах времени выполнения RTTI (RunTime Type Information). То есть, базовым важным свойством в данном случае является наличие этой самой информации RTTI, а не конкретный способ доступа published.
published -- это не только наличие RTTI, но и ещё прямое указание средствам разработки (визуальному редактору форм в составе IDE) о том, что данное свойство должно появляться в инспекторе объектов, чтобы разработчик мог на него любоваться, а также менять, когда нужно (если это свойство допускает изменение в принципе, конечно). public-свойства в инспекторе объектов не появляются, поскольку они не нужны человеку при создании приложения.
Yoda писал(а):
SII писал(а):
в Дельфях невозможно сделать автоматическую сериализацию определённых свойств объектов (нет, в принципе, это возможно -- но только написанием внешнего дополнительного компилятора, управляемого особыми комментариями подобно макросам в Це++, а это не шибко элегантно и т.д. и т.п.). Ну а отсюда, собственно, вопрос: не задумывались ли, можно ли подобную задачу реализовать чисто средствами самого языка?
Я так понимаю, автоматическая сериализация возможна только в тех языках, которые полностью берут на себя управление памятью, и никак иначе.
Полностью автоматическая -- да. Но вот поддержка "полуавтоматической" на уровне языка была б полезна, думается -- в виде атрибутов полей для автоматической сериализации и возможности указать методы для ручной. Есть же методы-конструкторы и деструкторы, которые синтаксически отличаются от обычных методов внутри класса (в Си++ их имена совпадают с именем класса, только у деструктора ещё ~ впереди добавлен; в Дельфях имена могут быть произвольными, но перед ними вместо обычной procedure или function стоит constructor или destructor). Думается, можно ввести ещё сериализаторы/десериализаторы; без наличия таковых в некоем классе выполняется лишь автоматическая (де)сериализация полей, имеющих соответствующий атрибут, а при наличии -- после автоматической вызывается соответствующий метод.