Pull to refresh

Comments 35

Посмотрел пример — у вас просто генерируются функции — геттеры и сеттеры. А в коде все равно приходится вызывать get_x() и set_x()
Интересно когда свойства интегрируются в язык, так чтобы они синтаксически были не отличимы от переменных.

Ага, когда читал, подумал, что можно было бы сделать хитрое пубичное поле с переопределёнными операторами присваивания для установки/чтения значения, но статья как-то внезапно закончилась.


Я так не пробовал, просто пришедшая в голову идея:


struct Person{
    Property<std::string> name("");
    Property<int, private_setter=true> id(0, setter=[](int value){ assert(value >= 0); return value;});
}
Переопределение операторов скорее всего не будет работать. Можно переопределить operator=, но стоит только написать например "+=" и все сломается.
Нужна именно языковая поддержка. Не понимаю почему в С++ до сих пор не ввели. Помнится, в древнем С++Builder'е чуть ли не 98 года свойства уже были.
Странно что автор не рассмотрел действительно классику — перезагрузку оператора =. Но по вашему замечанию ни чего не мешает и += перегрузить и все остальные составные присваивания. Да приседаний много но оно будет работать всегда и везде
К сожалению, совсем полноценный сеттер так реализовать не получится. Присваивание перегрузить можно, но вот допустим передать объект по неконстантной ссылке (для изменения как выходной параметр функции например) уже нет. Точнее получится, но не получится отличать геттер от сеттера, нет такого синтаксиса в С++, чтобы отличить чтение от записи. Можно перегрузить operator (const T&) и он будет вызываться, но только если объект явно имеет тип const.
Да иногда хочется иметь не const, но это первый признак проблем в архитектуре. Потому что развивая эту мысль можно договориться до того что выражение типа a=b+c может изменить b или с.
Контрпример.
Например иметь счётчик использований объекта. Почему нет?
Но для этих случаев есть mutable, который можно использовать с совместно const.
Макросы — зло.

Макросы, генерирующие исполняемый код — зло в квадрате.
Хуже только макросы, генерирующие несколько функций сразу.

Код с использованием макросов, генерирующих исполняемый код, сложен в понимании, практически не поддаётся отладке, сложен для модификации и поддержки. И этот букет приятных свойств распространяется соседствующие участки здорового кода.

koowaah, в аду для вас уже готовят персональный котёл.
Вы можете себе позволить отказаться от макросов лишь потому, что кто-то использует макросы за Вас. ©
Видимо вы никогда не поддерживали легаси-код с применением параметризованных макросов, которые разворачиваются в несколько функций/методов. Вам повезло, но вы меня не поймёте.
Встречал как раз такие макросы. Не спорю, это не слишком приятно, и если у Вас есть другие способы избежать копипасты в соответствующих случаях, я с радостью послушаю. Просто я о таких не знаю.
Также интересно как Вы реализуете без макросов debug-assert'ы, кроссплатформенные интерфейсы для взаимодействия с системным API и тому подобные вещи.
ИМХО применение макросов в С++ должно ограничиваться только следующими вариантами:
1)Условная компиляция (#ifdef и т.п.)
2)Включение файлов (без алтернативно).
3)Дополнительные параметры для компилятора (#pragma)
Подстановка констант — ЗЛО. Для этого есть const и constexpr.
Подстановка частей кода — АБСОЛЮТНОЕ ЗЛО. Функции вам в руки. Инлайнинг рулит.
Исключение только использование макросов из системного (WinAPI) и библиотечного кода (Qt).
А как избегаете копипасты в однотипных конструкциях?
Тут нужен пример копипасты.
Стандартно если копипаста больше пары строк то делаю функцию. Часто статическую.
Пример из ненастоящего (пет) проекта: есть классы ресурсов, и нам нужно от пользователя скрыть их объявления (потому что там используются всякие API, о которых он не должен знать), но в то же время пользователь должен иметь возможность оперировать этими ресурсами как сущностями (в смысле получать, передавать как аргумент и получать общую информацию). Для этого делаем специальные классы дескрипторов на ресурсы, которые хранят указатель и умеют вызывать нужные функции ресурса. Шаблоны тут не прокатят — нужно вызвать функцию класса ресурса, значит нужно инклуднуть его объявление, значит пользователь будет о нем знать. Поэтому делаем макросы DECLARE_RESOURCE_DESCRIPTOR и DEFINE_RESOURCE_DESCRIPTOR, которые порождают объявление и определение дескриптора соответственно. Первый используем в заголовке, другой — в cpp-шнике. Проблема решена.
Это относится как раз к одному из исключений. Скрывать что то от пользователя имеет смысл только в библиотеке. Описанная вами структура один в один реализована в Qt.
Но у них есть например и статические функции для этого.
И вообще в области библиотек для меня идеал это STL где макросы для пользователя отсутствуют.
Похоже на pimpl, нет?
Почему не сделать так
// A.h
class AImpl;
class B {
public:
    void a();
private:
   AImpl *impl;
}

// A.cpp
#include "AImpl.h"
void A::a() {
   impl->a();
}
Похожий код как раз и порождают макросы, а копирование этого класса для всех ресурсов — и есть проблема копипасты.
Тогда можно сказать что любое метапрограммирование — зло. Вы не видите кода, вы видите лишь какой-то генератор, который что-то генерирует.
Конечно, лексические макросы С/С++ не подарок, это очень древнее, кривое и уже давно устаревшее решение; лучше бы были синтаксические с глубокой интеграцией в компилятор и среду разработки с возможностью REPL и просмотра сгенерированного кода, но что есть — то есть.
А макросы, которые генерируют несколько функций — вполне нормально, если это какие-то неразрывно связанные функции (ну как геттер и сеттер), и изменение кода одной неизбежно влечет изменение кода другой.

Что лучше умозрительность или очевидность — в прямом смысле слов.
Ответ неоднозначен — кому как.

Крайне прямолинейное решение надуманной проблемы.

Если стоит задача иметь класс с полями — то или эти поля независимо изменяемы и инвариант обеспечивается внешними функциями, либо обеспечение инварианта лежит на самом классе, но тогда кодогенерация геттеров и сеттеров не имеет смысла, ибо каждый геттер обеспечивает специфичные проверки инварианта, а сеттеры — необходимые преобразования полей.

Иметь генерированные однообразные геттеры и сеттеры, который не обеспечивают инвариант — значит утроить количество поддерживаемого кода( и утроить кол-во юниттестов для этого кода)
Геттеры и сеттеры полезны не фактом своего существования, а лишь тогда, когда позволяют инжектировать какую-то полезную логику.
Часто при отладке бывает удобно ставить брякпоинты в сеттеры/геттеры — отлавливать обращения к полям. К тому же иногда приходится, с течением времени, заменять имплементацию классов — например, хранить данные в другом месте. Без геттеров/сеттеров тогда беда.
Бряки и на доступ к памяти можно ставить.
Бывает, нужно перехватить обращение к целой группе полей (например к массиву объектов). На каждый объект бряк по памяти не поставишь, а были бы свойства — одна легкая модификация кода и готово.
На изменение значения — можно, но довольно муторно, при каждом перезапуске все бряки надо заново переставлять на каждый объект. На чтение в VS, например, бряк не поставишь.
Использую фреймфорк Qt, можно видет что каждый класс для доступа к полям имеет геттеры и сеттеры.

Использую макросы для генерирования геттеров и сеттеров, уменьшает код класса. А используя type traits можно обезопасить себя от неверного указания типа и переменной.
Получается выстрелить себе в ногу не получиться.
Или нужны данные — тогда не нужны геттеры-сеттеры, полей достаточно. И проперти, которые просто вложенная структура с двумя методами + синтактический сахар, они тоже не нужны.
Либо нужны логика и поведение — тогда нужны полноценные функции-члены. В идеале так.
И геттеры-сеттеры, и проперти — это артефакт, появившийся из-за интерфейсов, как vtable. У интерфейса есть только указатели на методы, вот и приходится оборачивать доступ к «виртуальным полям» в геттеры, проперти и сеттеры.
Как-то так.
PS. В дизайн-тайме GUI они нужны — чтобы заполнить данные из записанных значений в дизайнере экранной формы: в поле напрямую записать нельзя, а метод интерфейса вызвать можно.
Поковыряйте исходники UE4, я готов сразу пожелать удачи, там макрос на макросе и макросом погоняет. Там ничего не поможет. Привнося макросы вы так же вносите хаос в свой код. Свойства можно реализовать на базе шаблонов и работать будут они как настоящие свойства. Когда опыт позволяет реализовать более или менее хорошо свойства, уже приходит понимание, что они не нужны, C++ уже принимается таковым, какой он есть.
Всё таки я никак не пойму, почему разработчики C++ за столько лет развития языка так и не добавили такую полезную штуку. Взять к примеру C#, Delphi в этих языках свойства присутствуют с самого начала и очень удобно этим пользоваться.

На уровне использования наличие свойств лишь предоставляет синтаксический сахар доступа к полю объекта.


pCPP->Feature() = new Feature(); // Для простых случаев без инварианта
pCPP->Feature(new Feature());  // Для претензией на сохранение инварианта

pCSharp.Feature = new Feature();

На уровне реализации свойства в С# страдают тем же врожденным недостатком — если логика eсть, то уникальная в каждом свойстве — и её надо писать и, руководствуясь "Явное лучше неявного" и проблемами при многоступенчатой инициализации сложносоставных объектов, имеет смысл выделять такие функторы в явные независимые сущности или отдельных методы с явными именами, а если логики нет — get; set; составляют визуальный мусор.


Концептуально, понятие "свойство" неотделимо от "поле объекта". Наличие или отсутствие синтаксиса — на усмотрении авторов языка и я не могу делать вывод хорошо\плохо без понимания проблемы, которую решали авторы C# и Delphi вводя синтаксис свойств в дополнение к методам и полям объекта.

Оговорочка: автор (одна штука).
Возможно поэтому они выглядят так похоже.
А почему вы выбрали хаб Си? В Си нет классов и методов, чем программистам на Си может быть интересна ваша статья?
Sign up to leave a comment.

Articles