Свойства в 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
