Свойства (properties) для C++
Но зачем?
Иногда программисту, который помимо С++ работает с другими языками, очень не хватает свойств объектов.
Казалось бы, языки вроде Java и C++ обходятся без свойств объектов, таких например как в Ruby, Python, JavaScript или Delphi.
Однако, предпринимаются попытки перенести свойства в C++ (например как это делает Qt), в связи с их преимуществами:
1) Изменение реализации без изменения интерфейса — просто меняем или убираем методы доступа (аксессоры)
2) Лаконичность и понятность: circle.radius = 3; вместо circle.setRadius(3);
3) Возможность «утиной типизации»
Кажется, идея заманчивая. И я решил попробовать.
Почему велосипед?
Проблема фреймворков с поддержкой свойств вроде Builder с его __property и QT c его Q_PROPERTY в их размерах или привязанности к компилятору.
Отдельно небольшой минус использования Qt в том, что все properties используют QVariant, что напрочь убивает статическую типизацию и привязывает программиста к системе конверсии вариантов (привет Q_DECLARE_METATYPE ;) )
Чего хотим от своей реализации?
1) Наши свойства должны хранить значения переменной
2) Свойства должны поддерживать функию-getter. При ее отсутствии отдавать значение как переменную. Должно работать как value = object.property; (Де-факто, получилось value = object.property.get() из-за ограничений языка)
3) Свойства должны поддерживать функию-setter. При ее отсутствии устанавливать значение как переменную.
Должно работать как object.property = value;
4) Свойства можно делать read-only (должна вылетать runtime-ошибка при присвоении)
5) Минимум зависимостей
6) Сохранить статическую типизацию
На мой взгляд, для простой реализации этого списка должно хватить с головой.
К интерфейсам!
Для начала, чтобы не раздражать читателя техническими подробностями реализации, приведем пример того, как получилось. Простая копипаста использования:
class Test {
properties
public:
property<std::string, Test> st;
property<int, Test> st1;
property<bool, Test> st2;
property<bool, Test> st3;
property<std::string, Test> st4;
property<std::string, Test> st5;
…
// Установим аксессоры прямо в конструкторе, но можно и позже (последний bool устанавливает Read Only)
Test(): st5(property<std::string, Test>(«Значение по умолчанию», this, &Test;::testGetter, &Test;::testSetter, true)) {
expose_property(st1);
expose_property(st);
expose_property(st2);
…
Более подробный пример работы со свойствами можно посмотреть здесь: github.com/Cabalbl4/cpp-properties/blob/master/main.cpp
Но как?
Как видно, properties статически типизированы и, так как являются шаблонными классами, принимают тип хранимой переменной и имя класса, в котором хранятся. К сожалению, без имени класса не обойтись, т.к. не возможно сделать указатель на член другого класса без этого параметра.
Макрос properties скрывает инициализацию карты свойств _properties, которая появляется у объекта. Свойства добавляются в эту карту уже в конструкторе макросом expose_property(имя свойства);
Теперь свойства можно перечислять, но в карте хранятся указатели на безтипового предка класса property, base_property. Следует отметить, что использование этих макросов опционально.
Небольшой сложностью является то, что base_property отдает только тип хранимой переменной, но не ее саму. Но это можно обойти за счёт приведения к потомку, т.к. его тип известен, например так: property<int, Test>* castedProperty = dynamic_cast<property<int,Test>*>(someBasicIntProperty);
Что потребуется для работы?
Требуется С++ 2011 + RTTI.
Также потребуется property.h с моего репозитория github github.com/Cabalbl4/cpp-properties
Еще примеры и описания
Можно найти еще примеры в описании к репозиторию и файле main.cpp
Надеюсь, кто-то найдёт мои простейшие попытки создать property полезными! :)
Иногда программисту, который помимо С++ работает с другими языками, очень не хватает свойств объектов.
Казалось бы, языки вроде Java и C++ обходятся без свойств объектов, таких например как в Ruby, Python, JavaScript или Delphi.
Однако, предпринимаются попытки перенести свойства в C++ (например как это делает Qt), в связи с их преимуществами:
1) Изменение реализации без изменения интерфейса — просто меняем или убираем методы доступа (аксессоры)
2) Лаконичность и понятность: circle.radius = 3; вместо circle.setRadius(3);
3) Возможность «утиной типизации»
Кажется, идея заманчивая. И я решил попробовать.
Почему велосипед?
Проблема фреймворков с поддержкой свойств вроде Builder с его __property и QT c его Q_PROPERTY в их размерах или привязанности к компилятору.
Отдельно небольшой минус использования Qt в том, что все properties используют QVariant, что напрочь убивает статическую типизацию и привязывает программиста к системе конверсии вариантов (привет Q_DECLARE_METATYPE ;) )
Чего хотим от своей реализации?
1) Наши свойства должны хранить значения переменной
2) Свойства должны поддерживать функию-getter. При ее отсутствии отдавать значение как переменную. Должно работать как value = object.property; (Де-факто, получилось value = object.property.get() из-за ограничений языка)
3) Свойства должны поддерживать функию-setter. При ее отсутствии устанавливать значение как переменную.
Должно работать как object.property = value;
4) Свойства можно делать read-only (должна вылетать runtime-ошибка при присвоении)
5) Минимум зависимостей
6) Сохранить статическую типизацию
На мой взгляд, для простой реализации этого списка должно хватить с головой.
К интерфейсам!
Для начала, чтобы не раздражать читателя техническими подробностями реализации, приведем пример того, как получилось. Простая копипаста использования:
class Test {
properties
public:
property<std::string, Test> st;
property<int, Test> st1;
property<bool, Test> st2;
property<bool, Test> st3;
property<std::string, Test> st4;
property<std::string, Test> st5;
…
// Установим аксессоры прямо в конструкторе, но можно и позже (последний bool устанавливает Read Only)
Test(): st5(property<std::string, Test>(«Значение по умолчанию», this, &Test;::testGetter, &Test;::testSetter, true)) {
expose_property(st1);
expose_property(st);
expose_property(st2);
…
Более подробный пример работы со свойствами можно посмотреть здесь: github.com/Cabalbl4/cpp-properties/blob/master/main.cpp
Но как?
Как видно, properties статически типизированы и, так как являются шаблонными классами, принимают тип хранимой переменной и имя класса, в котором хранятся. К сожалению, без имени класса не обойтись, т.к. не возможно сделать указатель на член другого класса без этого параметра.
Макрос properties скрывает инициализацию карты свойств _properties, которая появляется у объекта. Свойства добавляются в эту карту уже в конструкторе макросом expose_property(имя свойства);
Теперь свойства можно перечислять, но в карте хранятся указатели на безтипового предка класса property, base_property. Следует отметить, что использование этих макросов опционально.
Небольшой сложностью является то, что base_property отдает только тип хранимой переменной, но не ее саму. Но это можно обойти за счёт приведения к потомку, т.к. его тип известен, например так: property<int, Test>* castedProperty = dynamic_cast<property<int,Test>*>(someBasicIntProperty);
Что потребуется для работы?
Требуется С++ 2011 + RTTI.
Также потребуется property.h с моего репозитория github github.com/Cabalbl4/cpp-properties
Еще примеры и описания
Можно найти еще примеры в описании к репозиторию и файле main.cpp
Надеюсь, кто-то найдёт мои простейшие попытки создать property полезными! :)
0 комментариев