avatar

Свойства (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 полезными! :)
6 комментариев RSS
avatar
Очень хорошо свойства сделаны в дотнете, но для такого нужно спецификацию самих плюсов расширять.

public class Circle: Shape
{
private double _radius = 0;
public double Radius
{
get
{
return _radius;
}
set
{
_radius = value;
reDraw();
}
}
}
avatar
В любом случае, свойства предусмотренные спецификацией выйдут лучше кустарных поделок. Если, конечно, не делать свой метакомпилятор :)
avatar
Я тут последнее время слегка раскуривал SFML и в процессе созерцания бесконечных setProperty / getProperty начал задаваться вопросом: зачем так яростно алкать свойства, если достаточно было сделать два метода — void Property(val) и val Property()?
Тогда вместо shape.setColor() и shape.getColor() достаточно было бы shape.color(), а использование пусть определяется вызовом. Вызов без аргументов — хотим значение, вызов с аргументом — задаём его. Просто же, и сразу видно преимущество перегрузки функций.
Но почему-то не пользуются.
avatar
Такой вариант возможен, но теряет половину плюсов свойств. А именно — перечисляемость и связанную с ней утиную типизацию. Что я попытался учесть в своей реализации.
В ней делается так:

for(std::map<std::string,base_property*>::iterator it =someObject._properties.begin(); it !=someObject._properties.end(); ++it) { std::cout << "property name: " << it->first << std::endl;; }

Это выведет все свойства объекта. Можно смотреть есть свойство или нет и на этом строить логику.

Опять же, это на вкус и цвет.
avatar
Перечисляемость — это хорошо.

for(const auto &a : someObj._properties) std::cout << "property\t" << a.first << std::endl;

Так?
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.