Свойства (property)

Максим Некипелов
Максим Некипелов

В реализации C++ от Microsoft и от Borland уже можно найти жалкое подобие свойств в виде этого:

struct A
{
	void Set(int value) { }
	int Get() { return 1; }
	__declspec(property(put=Set, get=get)) MyProperty;
};

К минусам:

1) Сеттер и геттер живут все еще не в свойстве и засоряют пространство имен класса

2) Невозможно получить указатель на поле (самый главный минус)

&A::MyProperty // ошибка

3) Код свойства (геттеров и сеттеров) может повторяться, а менятся только то, с чем взаимодействуем, но его все равно нужно копипастить. Как так? У нас же метапрограммирование

 

Мотивация использования данного чуда:

Наверное тут мотивация может отсутвовать только у мазохиста. Но может кто-то не понимает область использования свойств, поэтому демонстрация:

Control.SetPosition(Control.GetPosition() + { 100.f, 100.f });

Как видим не очень красиво. А вот так уже красиво (код превращается в то, что выше, только это компилятор генерирует сам):

Control.Position += { 100.f, 100.f };

Но это мелочи по сравнению с тем, что пойдет дальше. У вас есть класс шаблон, который как-то обрабатывает поля у объекта, которые передаются в шаблон, используя операторы =, /, +, -, +=, -= и т.д., а вы туда не можете передать ваш Position, потому что при его изменении нужно вызывать Get и Set, а писать отдельную специализацию шаблона под указатели на методы банально лень. Тут все таки метапрограммирование как никак.

 

Особенно станет обидно когда в C++23 добавят рефлексию, а у вас тут методами все обмазано. По нормальному это должно все делаться через свойства. Думаю многие из вас видели редакторы и уровней в играх, и контролов для пользовательких приложений и там было окошечко со свойствами какого-то объекта. Задача банальная, а без свойств перерастает в ужастики. Конечно, можно написать свои свойства, но выглядит это все очень колхозно с кучей костылей.

 

Я предалагю добавить в язык ключевые слова: property, set, set_const, get, get_const, value.

 

set - для создания скопа кода, который будет вызываться при использовании операторов =, +=, -= и т.д. у свойства.

get - для создания скопа кода, который будет вызываться для приведения свойства в тип данных, которое оно оборачивает, также внутри операторов +=, -= и т.д.

 

Т.е. свойство при += ведет себя так (псевдокод):

type temp = get();
temp += value;
set(temp);
return temp;

Для +:

return get() + right;

Для других операторов думаю принцип работы понятен.

 

property также как class может взаимодействовать с template. Объект-пустышку свойства возможно определить только внутри класса или структуры (то что имеет внутри себя this если говорить проще). На него можно получить указатель как на обычный член класса (это очень обязательное условие, иначе все идеи использования свойств совместно с рефлексией отпадают), только тип поля будет определятся по типу которое наследует свойство (чтобы при доставании типа данных через специализацию, которое храни свойство, все было хорошо). Внутри скопа также будет доступен this, который будет указателем на класс, внутри которого находится свойство. Теперь пример  кода:

#include <iostream>

// Внешнее определение нового шаблонного свойства, которое устанавливает значение полю возводя его во вторую степень
template<auto Field>
property PPow2 : float // value может быть float
{
	set // скоп сеттера, работает только внутри скопа property
	{
		(this->*Field) = value * value; // this это Vehicle*, value содержит новое значение, действует только внутри set и set_const
	}

	get_const // скоп константного геттера, работает только внутри скопа property
	{
		return this->*Field; // this это const Vehicle*, const из-за get_const, в обычном get указатель будет не константный
	}
};

class Vehicle
{
private:
	float _Health = 0.f;

	void AddSeat()
	{
		std::cout << "NEW SEAT" << std::endl;
	}

	unsigned _MaxPassenger = 0;

public:
	PPow2<&Vehicle::_Health> Health;

	// Внутреннее определение свойства (без имени)
	property : unsigned
	{
		set
		{
			for (unsigned i = 0; i < value; ++i)
				AddSeat();

			_MaxPassenger = value;
		}

		get_const
		{
			return _MaxPassenger;
		}
	}
	MaxPassenger = 100;
};

constexpr unsigned Vehicle::* MaxPassenger = &Vehicle::MaxPassenger;

int main()
{
	Vehicle infernus;
	
	infernus.*MaxPassenger = 1337;
	infernus.Health = 100.f;
}

 

Думаю объяснил все максимально кратко и доступно. Если какой-то момент не понятен могу дополнить. Возможно уже кто-то предлагал данную идею, но я не нашел.

0
рейтинг
Нет комментариев
Другие идеи
Группа создана, чтобы собирать предложения к стандарту C++, организовывать их внутренние обсуждения, помогать готовить их для отправки в комитет и защищать на общих собраниях в рабочей группе по С++ Международной организации по стандартизации (ISO).
Все предложения