Справочник функций

Ваш аккаунт

Логин:
Пароль:
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:
Убрать блок ВКонтакте навсегда

Последние темы форума

Показать новые сообщения »

Почтовая рассылка



Подписчиков: 19017
Последний выпуск: 23.04.2012

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

Автор: Алексей Носков, http://blog.alno.name/
Дата: 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;
}

Ссылки

Оставить комментарий

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

Если вы не являетесь зарегистрированным пользователем, то вам необходимо зарегистрироваться. Регистрация бесплатна. Если вы уже зарегистрированы на CodeNet, то вам необходимо ввести логин и пароль в верхней (Alt-U) части страницы.

Комментарии

1. seyko / 28 сентября 2008, 16:28:01
Мне нравитсяМне не нравится

При включённой опции -Wall gcc 3.4.6 ругается на использование 0 как адреса базового класса. Если взять для примера 4 вместо 0 и потом вычесть дополнительно эту 4, то нет предупреждений, всё работает по-прежнему и никаких доп. расходов тоже нет.

А вот вывод свойства в поток без явного указания типа выводит адрес вместо 145
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог
ремонт плоттеров hp 500 недорого