Свойства в C++
Автор: Алексей Носков, http://blog.alno.name/
Дата: 26 мая 2008 года
Дата: 26 мая 2008 года
Немного поигравшись, пришел к реализации свойств в C++, которая обладает некоторыми преимуществами, по сравнению с известными мне реализациями:
- Свойства не требуют инициализации в конструкторах
- Независимо от количества свойств, размер класса увеличивается на константу, связанную с выравниваем членов. У меня, например, на 4 байта.
Как это делается?
Реализация свойств
Основные идеи реализации следующие:
- Шаблон класса свойства не содержит полей! Геттеры и сеттеры передаются в класс в виде шаблонных параметров, о том как извлекается указатель на объект-владелец чуть позже. Эта особенность имеет два важных следствия:
- Длина класса минимальна, определяется компилятором
- Можно хранить класс в занятой памяти. То есть, можно создать 100 свойств в одном и том же участке памяти, они не будут перекрываться по данным, потому что данных у них нет.
- Все классы свойств упаковываются в union, чтобы лежать в одном и том же участке. Именно поэтому размер класса не зависит от количества свойств.
- Также в этот union добавляется член __properties, используемый для того, что свойства могли определять смещение union относительно начала класса.
- Зная смещение, каждое свойство определяет адрес объекта-владельца вычитая смещение из собственного адреса. Это вычисление вынесено в отдельный метод в классе properties
- Чтобы скрыть реализацию, определяются несколько макросов, упрощающих работу с этой структурой.
- Ниже приведен код, предоставляющий реализацию
/** * Класс, предоставляющий общие сервисы для свойств, а также * используемый для хранения в классе позиции свойств. */ template < typename PropertyOwner // Класс владельца > class properties { public: // Получить указатель на владельца по указателю на свойство static PropertyOwner * owner( void * property ) { int aai = (int)&(((PropertyOwner*)0)->__properties); return (PropertyOwner *)((char*)property - aai); } }; /** * Шаблон класса свойства */ template < typename PropertyOwner, // Класс владельца typename PropertyType, // Тип свойства PropertyType (PropertyOwner::*getter)(), // Геттер void (PropertyOwner::*setter)(PropertyType) > // Сеттер class property { public: /** * Чтение свойства - вызов геттера */ operator PropertyType() { return (properties<PropertyOwner>::owner( this )->*getter)(); } /** * Запись в свойство - вызов сеттера */ void operator = ( const PropertyType & value ) { (properties<PropertyOwner>::owner( this )->*setter)( value ); } }; // Макросы для удобного определения свойств ///////// /** * Начать объявления свойств в классе cls */ #define properties_start(cls) union { properties<cls> __properties; /** * Закончить объявление свойств в классе cls */ #define properties_end() }; /** * Объявить свойство в классе cls типа type c геттером getter и сеттером setter */ #define property(cls,type,getter,setter) property<cls,type,&cls::getter,&cls::setter>
Использование свойств
Как объявить свойства в классе?
- Все свойства объявляются в блоке, который начинается вызовом properties_start(<classname>) и заканчивается properties_end().
- Внутри блока каждое свойство объявляется конструкцией property( cls, type, getter, setter ), аргументы которой - имя класса, тип свойства, имя метода-геттера, имя метода-сеттера.
Вот пример, демонстрирующмй объявление:
class CClass { private: int a_value; /** * Геттер */ int getA() { return a_value; } /** * Сеттер */ void setA( int a ) { a_value = a; } public: properties_start( CClass ); // Начало свойств property( CClass, int, getA, setA ) a; // Свойство properties_end(); // Конец свойств };
Использование свойств:
int main( int argc, char ** argv ) { CClass c; c.a = 145; // Запись свойства int aa = c.a; // Чтение свойства return 0; }
Ввод и вывод для свойств
Для того, чтобы корректно работать с вводом и выводом для свойств, я добавил две шаблонные функции, позволяющие записывать свойства в поток и читать их оттуда. Все это работает, если тип свойства можно писать в поток или читать из него.
template < typename PropertyOwner, typename PropertyType, PropertyType (PropertyOwner::*getter)(), void (PropertyOwner::*setter)(PropertyType) > std::ostream & operator << ( std::ostream & os, property<PropertyOwner,PropertyType,getter,setter> prop ) { return os << (PropertyType)prop; } template < typename PropertyOwner, typename PropertyType, PropertyType (PropertyOwner::*getter)(), void (PropertyOwner::*setter)(PropertyType) > std::istream & operator >> ( std::istream & is, property<PropertyOwner,PropertyType,getter,setter> prop ) { PropertyType value; is >> value; prop = value; return is; }
Ссылки
- http://dask-blog.blogspot.com/2008/05/property-c.html
- Статья на RSDN, которая послужила отправной точкой реализации
Оставить комментарий
Комментарии
1.
+1 / -0
28 сентября 2008, 16:28:01
При включённой опции -Wall gcc 3.4.6 ругается на использование 0 как адреса базового класса. Если взять для примера 4 вместо 0 и потом вычесть дополнительно эту 4, то нет предупреждений, всё работает по-прежнему и никаких доп. расходов тоже нет.
А вот вывод свойства в поток без явного указания типа выводит адрес вместо 145
А вот вывод свойства в поток без явного указания типа выводит адрес вместо 145