Немного поигравшись, пришел к реализации свойств в 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;
}
Ссылки
|