Инкапсуляция представления объекта (его состояния, его полей данных) является ключевым моментом в ООП. Прямой неконтролируемый доступ к полям данных объектов нежелателен. Для контролированного опосредованного изменения состояния объекта применяют методы чтения/записи (get/set). С помощью этих методов реализуется безопасное изменение состояние объекта.
При некотором размышлении над проблемой реализации свойств на ум приходят следующие факты:
В качестве возможного решения можно описать шаблон класса, параметризованный исходным типом «свойства». Для того, чтобы экземпляр этого класса могли вызывать методы доступа, надо передать ему указатель на объект-носитель свойства, а также указатели на методы доступа. Причём все эти указатели надо передать заранее, до начала использования «свойства», то есть в конструкторе объекта-носителя. Следовательно, при таком подходе каждое новое «свойство» потребует дополнительно по 3 указателя.
Так как «свойство» описывается шаблонным классом (а шаблон можно параметризовать не только типами, но и значениями), можно передать шаблону указатели на методы доступа ещё на этапе компиляции. Главное, чтобы методы были не виртуальными, тогда их адреса будут статически известны на этапе компиляции. Но как избавиться от хранения третьего указателя от указателя на объект-носитель «свойства»?
В начале я не нашёл ничего лучшего как реализовать динамическое вычисление указателя на объект-носитель через указатель на объект самого «свойства». То есть задача свелась к определению адреса объекта через адрес его поля данных. Но компилятор готов «раскрывать» адрес поля данных объекта только при условии знания имени этого поля данных. То есть можно статически определить смещение поля данных в некотором классе, указав имя этого поля.
Но шаблон нельзя параметризовать именем поля, а передать ему статически вычисленное смещение также не удастся по простой причине: невозможно вычислить смещение ещё не описанного поля. Дело в том, что при описании поля данных мы должны конкретизировать шаблон смещением этого поля данных. Но оно ещё не описано, оно только в процессе описания. Таким образом, я пришёл к симбиозу шаблона и макроса.
Такое решение получилось небезопасным и зависящим от компилятора. В самом деле, динамическое вычисление адреса объемлющего объекта опиралось на конкретное бинарное линейное представление вложенных объектов. ');
// вычисление в классе B адреса объемлющего объекта класса А через адрес поля данных A::b класса B B * Owner () { // берём смещение поля A::b B PopertyHost::*pb = & A::b; // базируемся относительно своего this return ( A* ) ( size_t(this) - *((size_t *)&pb) );
Я обратился на форум RSDN и сразу же получил ответ, который вполне удовлетворил мои требования. Трюк заключался в том, чтобы сделать «свойство» не полем данных, а методом, который возвращает объект некоторого класса, для которого перегружены необходимые операторы. При этом решается задача с определением this, так как кажый нестатический метод неявно получает this своего объекта. Предложенное решение безопасно, так как компилятор не допустит описание «свойства» за пределами класса.
// Шаблонный прототип свойства с доступом чтение/запись template < class Owner, class T, T (Owner::*Getter) (), void (Owner::*Setter) ( T ) > struct PropertyRW_ { Property ( Owner & owner ): owner_ ( owner ) {} operator T () { return (owner_.*Getter) (); } T operator () () { return (owner_.*Getter) (); } void operator = ( T value ) { (owner_.*Setter) ( value ); } Owner & owner_; }; // Макрос свойства с доступом чтение/запись #define PropertyRW(Name, Owner, T, Setter, Getter) \ PropertyRW_<Owner, T, &Owner::Setter, &Owner::Getter> Name () \ { \ return PropertyRW_<Owner, T, &Owner::Setter, &Owner::Getter>(*this); \ }
Опишем фрагмент класса-носителя «свойства».
class XML { public: // ... // Методы доступа к "свойствам" const string & GetName () { return name_; } void SetName ( string name ) { name_ = name; } const XML * GetParent () { return parent_; } void SetParent ( const XML * parent ) { parent_ = parent; } // "Свойства" PropertyRW ( name, XML, const string &, GetName, SetName ); // порождает метод name () PropertyRW ( parent, XML, const XML *, GetParent, SetParent ); // порождает метод parent () private: // Скрытые исходные поля данных "свойств" string name_; XML * parent_; };
Работа со «свойствами» будет выглядеть почти также как и с настоящими свойствами. Одно из существенных отличий заключается в том, что для вызова методов класса поля данных, которое прикрывается «свойством», необходимо явно указать дополнительные пустые скобки.
XML xml; xml.name() = "sample"; // Преобразование к типу поля данных XML::name_ - string cout << "XML-элемент назван " << xml.name() << endl; // Вызов перегруженного оператора () для получения копии поля данных XML::name_ cout << "Длина имени элемента составляет " << xml.name()().length () << " символ(ов)." << endl;