Свойства в C++

На тему http://dask-blog.blogspot.com/2008/05/property-c.html.

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

Ссылки

 Подписаться на RSS

 #  #  #  #  #  #  #  #  #  #

Добавить комментарий