Pull to refresh

Comments 44

Мне кажется, что основная «эстетическая» проблема тут — отсутствие видимой декларации геттеров и сеттров. Это рвет шаблон.

А насчет макросов не негодуйте. Макросы — зло, их развивать никто не будет. Это даже хорошо, что нет перегрузки. Представьте какой зоопрак был бы.
Думаю, можно было бы сделать синтаксис такой, чтоб smartfield принимал имена геттера и сеттера в качестве аргументов. Опять же если сделать так то если программист забудет написать один из методов то компилятор выведет что-то страшное, а так как сделано сейчас только то, что такой-то метод не реализован.
На счет макросов на самом деле склонен с вами согласиться, но мне кажется здесь они к месту. Можно было обойтись почти без всех макросов начинающих с "__", они все появились в процессе проб и ошибок и пока я придумывал код использовал такие блоки как детали конструктора.
Было бы круто сделать объявления пропертей как в C#, но я не представляю как :)) Кстати, если вы завязаны на конкретный компайлер, то можно полазить по докам. Майкрософтовский С++, например, поддерживает проперти.

Ну вы же написали, что это просто этюд. Так что реализацию можно и не обсуждать глубоко.
Я тоже сначала хотел сделать по образцу C#, но меня смутил тот факт, что в С++, в отличии от шарпа, принято разделять декларирование и реализацию.

Вообще я почти всегда использую студию, но кроссплатформенностью жертвовать не хотелось бы, поэтому надеюсь найти время и написать вторую версию для любого компилятора и с возможностью указания имен геттеров и сеттеров и может еще какими-нибудь плюшками.
Это не сильно распрострено, но не является чем-то чуждым для С++. Так поступают с инлайнами в классах или с шаблонами.

Я вот это имел в виду:

class TimePeriod
{
private double seconds;

public double Hours
{
get { return seconds / 3600; }
set { seconds = value * 3600; }
}
}


Такое на С++ не напишешь :)
Нуууу… Может и не напишешь. Сходу я придумал что-то похожее только для доступа по имени из ран-тайма. Заведем в базовом классе (а я считаю, что наследование от базового класса это не плохо) виртуальный метод void _fieldsection(), который будем вызывать из конструктора. Заведем макрос #define fieldsection _fieldsection(). Теперь мы в классе можем написать так:
class Foo
{
    fieldsection
    {
        field(int, a)
        {
            get {...}
            set {...}
        }
    }
...
}

здесь field — макрос, который разворачивается в вызов метода добавление поля в класс, а get и set разворачиваются в лямбда функции, которые сохраняются соответственно текущему полю. Не могу только придумать, как сделать обращение через. или ->.
А что, мне нравится.
В принципе, если мне удастся запилить что-то вроде рефлексии — сделать действительно красиво как в шарпе будет не сложно.
Подскажите, может я торможу, но нельзя ли сделать конкатизацию при редекларировании дефайна предыдущего значения с новым:
#define MACRO мясо
#define MACRO есть_##MACRO
Надо чтоб получилось «есть_мясо», а генерится «есть_MACRO»
Нельзя. Имя макроса из макроса не раскрывается.
Какая-то жуткая смесь Си, C++ и С++0x с регулярным эксплуатированием багов компилятора студии. Гигантские макросы, которые разворачиваются в шаблоны, которые инстанциируются для классов, которые в свою очередь сгенерированы препроцессором. Мечта для любителей отладки.

А причина появления статьи просто гениальна: «в C++ нет средства описания полей класса с контролируемым доступом, как например property в C#». Конец объяснений (к слову, в прошлой статье то же самое). Опять же, полный игнор комментариев к предыдущей статье. Извините, но это совсем не fun.
Понятное дело, что никто не собирается это использовать в реальном проекте. Что плохого в том, чтобы в свое удовольствие написать что-то такое жуткое, но интересное? Кому-то это просто нравится, вот и все.
Плохое в том, что перед написанием кода неплохо бы задаться реальной практической целью, с которой пишется код. Люди видят, что в языках есть интроспекция и сразу накручивают макросы добавления метаданных к классу «чтобы просто были». Люди видят, что в языках есть рефлексия и сразу на каждое поле данных класса приделывают класс с теми самыми метаданными. Зачем нужна интроспекция и рефлексия никого не волнует.

А ведь можно было задаться целью написать систему сериализации/десериализации классов, в которых поля определены как «field(float, f);». Или приделать систему динамического добавления полей. В любом случае нужно задаться реальной целью до начала написания кода.
Не стоит во всем искать практический смысл.
Для бессмысленного кода есть блог «Ненормальное программирование».
Признаться честно, именно сериализация и рефлексия в Java меня подтолкнули меня написать это.
Точно. Практического применения никакого. Если property в C# сокращает написание кода, то тут абсолютно столько же кода нужно написать.
Тут больше пришлось :)
Интересно получилось. Как автор предыдущей статьи скажу, что у вас получилось гораздо красивее с точки зрения синтаксиса. Я, кстати, на той статье не остановился и немного развил идею . Красивее не стало, зато теперь намного безопаснее. К ваше реализации есть пара претензий:
1) Использование чисто майкрософтовских «фич», которые нарушают кроссплатформенность. В студии и так есть свои проперти.
3) Неявное задание сеттеров и геттеров, которое ещё и обязывает по-вашему называть методы.
2) Не смог до конца разобраться, но как создать readonly свойство?

У меня сейчас бродят идеи реализации на основе новшеств C++11. Лямбды вместе с std::function помогут сделать обьяления методов доступа красивее, а возможность вызывать конструкторы друг из друга — выделить инициализацию свойств в отдельный конструктор.
1) Уже взял на заметку и собираюсь с этим бороться
3) Я думал это фича, но судя по комментариям следует дать возможность программисту самостоятельно описывать методы get и set.
2) Не задумывался об этом, но smartfield + пустой сеттер (или который например кидает эксепшн)
На счет развития идеи — тоже взял на заметку, позже разберусь.
И на счет использования нового стандарта — эта идея мне действительно кажется хорошей и может быть получится сделать действительно пригодную для использования реализацию.
Я очень негодую отсутствую в препроцессоре возможности перегрузки макросов хотя бы по числу аргументов

На сколько я знаю, с помощью boost preprocessor'а можно указать параметр с любым количеством параметров или что-то похожее. Перегрузить конечно макрос нельзя, но можно вроде обойтись листом параметров. Вот небольшая тема как реализовать подобное на gcc без boost'а.
Про __VA_ARGS__ я конечно знаю и пробовал применить здесь, но ничего хорошего не вышло. А вообще иногда пригождается.
А по сути, зачем нужно переменное число параметров макроса? Чтобы передать такой же функции.
Вот ей богу, этот язык вечен, ибо не знает его никто.
>«Например поле типа int с именем «x». Нас вполне устроит такая запись: field(int,x);»
«отучаемся говорить за всех»©FIDO
меня устроит запись get_x() и set_x(v)
То есть, мы забили на все плюшки статической типизации, и сделали странный, кривой и неустойчивый динамический сеттер/геттер. А в чем профит?
Рефлексия, фабрики, де/сериализация…
По-моему, достаточно просто заполнить map<string, T>, и добавить к этому T &operator()(string). А в конструкторе заполнить мапу известными полями. Тогда такая запись:
obj("fieldName") = value; // имеет место быть

Ссылку на что вернет ваш оператор, если поля не существует?
По идее исключение можно вызвать. А можно просто T()
Т.е. малейшая опечатка и узнаете об этом только во время выполнения программы (исключение). Либо вообще не узнаете, а будете час судорожно искать почему у вас не работает приложение (в случае T()).
Такие вещи должны во время компиляции всплывать.
Я уж не говорю о том, что никакие вижуал ассисты и интеллисенсы не смогут вам подсказать имя поля.
Судорожно не буду, студия покажет. Вы рассматриваете опечатки или реализацию?
Есть у человека желания рефлексию заделать — пусть делает. Опечатки уже его проблема. Я лишь откоментил, что столько нагромождений, для обращения к члену по строке решаются проще.
Чем больше я смотрю на C++, тем больше нравится мне Ruby © перефразировал классика

class Foo
attr_accessor :x
end
Ну вы хотя бы из любви к рациональности заюзали в базовом классе хеш-таблицу вместо вектора с линейным поиском и сравнением строк на каждой итерации.
К слову, нечто отдаленно похожее реализовано в .NET Framework в классе DependencyObject. Но там цель всего этого заключалась в том, что бы можно было описывать граф распространения значений свойств набора объектов, как набор других объектов, строить цепочки связанных свойств. Проще говоря, для организации Data Binding'а.
Касаемо перегрузки макросов, есть такая штука как variadic macro.

Полного решения проблемы это не даёт, но можно через этот variadic macro сделать вызов разных перегруженных методов. Я это использовал в своей библиотеке журналирования для Qt, о которой пока не успел написать на Хабре.

Выглядит это примерно так:
#define LOG_DEBUG(...)   Logger::write(Logger::Debug, __FILE__, __LINE__, Q_FUNC_INFO, ##__VA_ARGS__)

...

class Logger
{
  public:
    ...
    static void write(LogLevel logLevel, const char* file, int line, const char* function,
                                                                     const QString& message);
    static void write(LogLevel logLevel, const char* file, int line, const char* function,
                                                                     const char* message);
    static QDebug write(LogLevel logLevel, const char* file, int line, const char* function);
    ...
}

В вызываемом коде:
LOG_DEBUG("Test1");
LOG_DEBUG(tr("Test2")); // QObject::tr() возвращает QString
LOG_DEBUG() << "Test 3:" << testValue; // У объекта QDebug есть перегруженный operator<<

Проверка типов на этапе компиляции работает. В принципе, никто не мешает вместо пачки статических функций сделать #define на шаблонизированную функцию.
Очередной костыль для С++. Ради чуть лучшей читаемости наворотили чёрти-чего и сбоку бантик. Зачем? С++ прекрасен и без этих конструкций.

Может пора пересесть на C#? Или на Delphi? Там это лет 15 назад уже было.

PS: Впрочем, как академические упражнения в написании макросов и шаблонов, довольно занимательно.
А что, на делфи еще пишут?))
Новые проекты практически не начинают, но зоопарк старых проектов за 15 лет стал настолько огромен, что работы еще на долгие годы хватит. Ну и Embarcadero изо всех сил старается, говорят, шаблоны недавно запилили.
Embarcadero, это что? Тенденция наметилась делфийский код в си-шарповский переделывать.
Кстати, главного затейшика делфи, давно переманили в MS.
Вас в гугле забанили? Это текущие владельцы наследия Borland.
В одном из своих проектов использую такой вариант:

class FullStrategyMapScene : public QGraphicsScene
{
Q_OBJECT;
public:
// Инициализируем свойства
FullStrategyMapScene() : properties(this) {;}
~FullStrategyMapScene();

// Объявляем свойства
PROPERTIES(FullStrategyMapScene,
// Простое свойство
PROPERTY(QSize, CellSize)
// Простое свойство с инициализацией
PROPERTY_I(Unit const*, CurrentUnit, NULL)
// Свойство с геттером и сеттером
RW_PROPERTY(QPoint, CursorPos, GetCursorPos, SetCursorPos)
// Свойство с сеттером
WO_PROPERTY(IPlanet const*, Planet, SetPlanet)
RO_PROPERTY(ISurfaceMap const*, SurfaceMap, GetSurfaceMap)
);

private:
void SetPlanet(IPlanet const* planet);
QPoint const& GetCursorPos() const;
void SetCursorPos(QPoint const& pos);
ISurfaceMap const* GetSurfaceMap() const;
};


Тоже не бог весть что, но позволяет достигнуть желаемого эффекта («описания полей класса с контролируемым доступом»).
А почему вы Q_PROPERTY не используете? Если уж юзаете Qt…
Потому что на Qt только часть часть проекта. Использовать Qt только для пропертей считаю не очень хорошей мыслью. Тем более, что взаимодействовать с Qt-свойствами всё равно надо через сеттеры и геттеры.
Sign up to leave a comment.

Articles