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

Ваш аккаунт

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

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

Показать новые сообщения »
реклама
Отзывы о VPS.ua всё ли так замечательно? Отзывы клиентов компании.

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

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

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

Ссылки

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

Комментарий:
можно использовать BB-коды
Максимальная длина комментария - 4000 символов.
 

Комментарии

1.
43K
28 сентября 2008 года
seyko
0 / / 28.09.2008
Мне нравитсяМне не нравится
28 сентября 2008, 16:28:01
При включённой опции -Wall gcc 3.4.6 ругается на использование 0 как адреса базового класса. Если взять для примера 4 вместо 0 и потом вычесть дополнительно эту 4, то нет предупреждений, всё работает по-прежнему и никаких доп. расходов тоже нет.

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