СвойстваЯ уже говорил о C++ Builder и хотя эта заметка не будет посвящена ему, тем не менее я о нем вспомню. Когда только появилась Delphi (первой версии) и ее мало кто видел, а еще меньше людей пробовали использовать, то в основном сведения об этом продукте фирмы Borland были похожи на сплетни. Я тогда был еще очень доверчив и мало чего знал, но от этого периода я отчетливо помню такое высказывание: "Delphi расширяет концепции объектно-ориентированного программирования за счет использования свойств". Перефразировав фразу из "Понедельник начинается в субботу" можно сказать, что я тогда был молод и верил во все, кроме существования бога ;)
С тех пор я работал с C++ Builder и представляю себе, что такое его свойства.
Кроме того, я теперь хоть как-то знаком с объектно-ориентированным анализом
и проектированием, что бы понимать --- наличие в языке той или иной конструкции
никак не влияет на используемые подходы. Мало того, префикс "ОО" обозначает не использование
ключевых слов Тем не менее, свойства позволяют использовать следующую конструкцию: описать функции для чтения и записи значения и связать их одним именем. Если обратиться к этому имени, то в разных контекстах будет использоваться соответственно либо функция для чтения значения, либо функция для его установки. В свое время я участвовал в споре, смысл которого сводился к следующему: свойства очень удобны и наглядны, поэтому их надо ввести в стандарт языка. Я тогда утверждал, что нагромождение лишнего приведет к тому, что C++ станет вообще непонятным языком. Кроме того, я тогда сказал, что, скорее всего, свойства в таком виде реализуются и уже существующим набором средств. Но перед тем, как я перейду к описанию реализации, я хотел бы высказаться по поводу удобства использования. Прямо скажем, программисты --- люди. Очень разные. Одним нравится одно, другое это ненавидят... в частности, возможность переопределения операций в C++ часто подвергается нападкам, при этом одним из основных аргументов (в принципе, не лишенный смысла) является то, что при виде такого выражения: a = b;
нельзя сразу же сказать, что произойдет. Потому что на оператор присваивания можно
"повесить" все что угодно. Самый распространенный пример "неправильного" (в смысле,
изменение исходных целей) использования операций являются потоки ввода-вывода, в которых
операторы побитового сдвига выполняют роль Тем не менее, перейду к описанию примера. Итак, нужно оформить такой объект, при помощи которого можно было бы делать примерно следующее:
class Test
{
protected:
int p_a;
int& setA(const int& x);
int getA();
public:
/* ... */ a;
};
// ...
Test t;
t.a = 10; // Вызов Test::setA()
int r = t.a; // Вызов Test::getA()
Естественный способ --- использовать шаблоны. Например, вот так:
template<class A, class T,
T& (A::*setter)(const T&),
T (A::*getter)()
>
class property
{
// ...
};
Параметр
На запись, которая используется для описания указателей на функции, стоит обратить
внимание --- я знаю, что многие программисты на C++ и не догадываются о том, что
можно получить и использовать адрес функции-члена класса. Строго говоря, функция-член
ничем не отличается от обычной, за исключением того, что ей требуется один
неявный аргумент, который передается ей для определения того объекта класса,
для которого она применятеся (это указатель Теперь непосредственно весь класс целиком:
template<class A, class T,
T& (A::*setter)(const T&),
T (A::*getter)()
>
class property
{
protected:
A * ptr;
public:
property(A* p) : ptr(p) { }
const T& operator= (const T& set) const
{
assert(setter != 0);
return (ptr->*setter)(set);
}
operator T() const
{
assert(getter != 0);
return (ptr->*getter)();
}
};
Я внес тела функций внутрь класса для краткости (на самом деле,
общая рекомендация никогда не захламлять определения классов
реализациями функций --- если нужен Я думаю, что идея предельно ясна. Использование указателей на функцию-член заключается как раз в строках вида: (ptr->*f)(); Указатель внутри свойства --- это, конечно же, нехорошо. Тем не менее, без него не обойтись --- указатель на объект нельзя будет передать в объявлении класса, только при создании объекта. Т.е., в параметры шаблона его никак не затолкать.
Использовать класс
class Test
{
// ...
property<Test, int, &Test::setA, &Test::getA> a;
Test() : a(this) { }
};
Компиляторы g++, msvc и bcc32 последних версий спокойно восприняли такое издевательство над собой. Тем не менее, более старые варианты этих же компиляторов могут ругаться на то, что используется незаконченный класс в параметрах шаблона, не понимать того, что берется адрес функций и т.д. Често говоря, мне кажется что стандарту это не противоречит. Но я не уверен в этом до конца --- тем более, что использовать это я не собираюсь. Тут я подошел к главному --- зачем все это нужно. А вообще не нужно ;) Я очень плохо себе представляю программиста, который решится на использование подобного шаблона --- это же просто нонсенс. Опять же, я не вижу никаких преимуществ по сравнению с обычным вызовом нужных функций и вижу один недостаток --- лишний указатель. Так что всю эту статью стоит рассматривать только как попытку продемонстрировать то, что можно получить при использовании шаблонов. На самом деле, теоретики "расширения концепций" очень часто забывают то, за что иногда действительно стоит уважать свойства. Это возможность сохранить через них объект и восстановить его (т.е., создать некоторое подобие persistent object). В принципе, добавить такую функциональность можно и в шаблон выше. Как? Это уже другой вопрос ;) РезюмеСвойства как расширение языка ничего принципиально нового в него не добавляют. В принципе, возможна их реализация средствами самого языка, но использовать такую реализацию бессмысленно. Догадываться же, что такой подход возможен --- полезно.
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
© 2000-2008, Andrey L. Kalinin mailto:andrey@kalinin.ru |
|