Rambler's Top100 Service калинин.ru / программирование / c и c++ /  << 23.08.00 >>

Свойства

Я уже говорил о C++ Builder и хотя эта заметка не будет посвящена ему, тем не менее я о нем вспомню.

Когда только появилась Delphi (первой версии) и ее мало кто видел, а еще меньше людей пробовали использовать, то в основном сведения об этом продукте фирмы Borland были похожи на сплетни. Я тогда был еще очень доверчив и мало чего знал, но от этого периода я отчетливо помню такое высказывание: "Delphi расширяет концепции объектно-ориентированного программирования за счет использования свойств". Перефразировав фразу из "Понедельник начинается в субботу" можно сказать, что я тогда был молод и верил во все, кроме существования бога ;)

С тех пор я работал с C++ Builder и представляю себе, что такое его свойства. Кроме того, я теперь хоть как-то знаком с объектно-ориентированным анализом и проектированием, что бы понимать --- наличие в языке той или иной конструкции никак не влияет на используемые подходы. Мало того, префикс "ОО" обозначает не использование ключевых слов class или property в программах, а именно подход к решению задачи в целом (в смысле ее архитектурного решения). С этой точки зрения, будут использоваться свойства или методы set и get, совершенно без разницы --- главное разграничить доступ.

Тем не менее, свойства позволяют использовать следующую конструкцию: описать функции для чтения и записи значения и связать их одним именем. Если обратиться к этому имени, то в разных контекстах будет использоваться соответственно либо функция для чтения значения, либо функция для его установки.

В свое время я участвовал в споре, смысл которого сводился к следующему: свойства очень удобны и наглядны, поэтому их надо ввести в стандарт языка. Я тогда утверждал, что нагромождение лишнего приведет к тому, что C++ станет вообще непонятным языком. Кроме того, я тогда сказал, что, скорее всего, свойства в таком виде реализуются и уже существующим набором средств.

Но перед тем, как я перейду к описанию реализации, я хотел бы высказаться по поводу удобства использования. Прямо скажем, программисты --- люди. Очень разные. Одним нравится одно, другое это ненавидят... в частности, возможность переопределения операций в C++ часто подвергается нападкам, при этом одним из основных аргументов (в принципе, не лишенный смысла) является то, что при виде такого выражения:

a = b;

нельзя сразу же сказать, что произойдет. Потому что на оператор присваивания можно "повесить" все что угодно. Самый распространенный пример "неправильного" (в смысле, изменение исходных целей) использования операций являются потоки ввода-вывода, в которых операторы побитового сдвига выполняют роль printf. Примерно тоже нарекание можно отнести и к использованию свойств.

Тем не менее, перейду к описанию примера. Итак, нужно оформить такой объект, при помощи которого можно было бы делать примерно следующее:

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
{
  // ...
};

Параметр A --- класс, к которому принадлежат функции установки и чтения значения свойств (они передаются через аргументы шаблона setter и getter). Параметр T --- тип самого свойства (например, int для предыдущего примера).

На запись, которая используется для описания указателей на функции, стоит обратить внимание --- я знаю, что многие программисты на C++ и не догадываются о том, что можно получить и использовать адрес функции-члена класса. Строго говоря, функция-член ничем не отличается от обычной, за исключением того, что ей требуется один неявный аргумент, который передается ей для определения того объекта класса, для которого она применятеся (это указатель this). Таким образом, получение адреса для нее происходит аналогично, а вот использование указателя требует указать, какой конкретно объект используется. Запись A::*foo говорит о том, что это указатель на член класса A и для его использования потребуется объект этого класса.

Теперь непосредственно весь класс целиком:

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)();
  }
};

Я внес тела функций внутрь класса для краткости (на самом деле, общая рекомендация никогда не захламлять определения классов реализациями функций --- если нужен inline, то лучше его явно указать у тела подпрограммы, чем делать невозможным для чтения исходный текст. Мне приходилось видеть исходные тексты, в которых определения классов занимали по тысяче строк из-за того, что все методы были описаны внутри класса (как в Java). Это очень неудобно читать.

Я думаю, что идея предельно ясна. Использование указателей на функцию-член заключается как раз в строках вида:

(ptr->*f)();

Указатель внутри свойства --- это, конечно же, нехорошо. Тем не менее, без него не обойтись --- указатель на объект нельзя будет передать в объявлении класса, только при создании объекта. Т.е., в параметры шаблона его никак не затолкать.

Использовать класс property надо следующим образом:

class Test
{
  // ...
  property<Test, int, &Test::setA, &Test::getA> a;

  Test() : a(this) { }
};

Компиляторы g++, msvc и bcc32 последних версий спокойно восприняли такое издевательство над собой. Тем не менее, более старые варианты этих же компиляторов могут ругаться на то, что используется незаконченный класс в параметрах шаблона, не понимать того, что берется адрес функций и т.д. Често говоря, мне кажется что стандарту это не противоречит. Но я не уверен в этом до конца --- тем более, что использовать это я не собираюсь.

Тут я подошел к главному --- зачем все это нужно. А вообще не нужно ;) Я очень плохо себе представляю программиста, который решится на использование подобного шаблона --- это же просто нонсенс. Опять же, я не вижу никаких преимуществ по сравнению с обычным вызовом нужных функций и вижу один недостаток --- лишний указатель. Так что всю эту статью стоит рассматривать только как попытку продемонстрировать то, что можно получить при использовании шаблонов.

На самом деле, теоретики "расширения концепций" очень часто забывают то, за что иногда действительно стоит уважать свойства. Это возможность сохранить через них объект и восстановить его (т.е., создать некоторое подобие persistent object). В принципе, добавить такую функциональность можно и в шаблон выше. Как? Это уже другой вопрос ;)

Резюме

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


Версия для печати


  Ссылки по теме:
Бъерн Страуструп
   Язык программирования C++, 3 издание.
  Рядом в разделе:
Комментарии (30.08.00)
   Плохое комментирование исходных текстов является одним из самых тяжелых заболеваний программ. Причем программисты зачастую путают "хорошее" комментирование и "многословное". Согласитесь, комментарии...   >>>>
Функция gets() (15.08.00)
   Функция gets(), входящая в состав стандартной библиотеки C, имеет следующий прототип: char* gets(char* s); Это определение содержится в . Функция предназначена...   >>>>
  Рядом по дате:
По прозвищу "Пуля" / Bullet, 1994 (24.08.00)
   Фильмы с Микки Рурком практически не бывают "совсем" плохими --- мне кажется, что это один из лучших американских актеров... в этом...   >>>>
Java 2: руководство разработчика (22.08.00)
   Я решил, что мне нужна книга по Java тогда, когда появилось требование что-то написать на этом языке более серьезное, чем рисование...   >>>>
  Содержание:
Заглавная страница
Мой блог
Мое резюме
Дайджест
Программирование
   C&C++
Сети
Unix
Алгоритмы
Оптимизация
Соревнования
Отвлеченно
XML
TeX
Просто так
Студенческое
Туризм
  Байки
Фотографии
Комментарии
   Книги
Web-ресурсы
Фильмы
Интернет
Программное обеспечение
Жизнь
Благодарности
Форум
Хронология
 
  В этом разделе:
Простой, но полезный аллокатор памяти (18.02.03)
   Эта заметка --- продолжение "Postfix изнутри" в том смысле, что в качестве примера опять берется postfix. Но если в прошлый раз...   >>>>
C или C++? (09.07.01)
   Существуют два диаметрально противоположенных, но одинаково распространенных мнения, которые можно выразить как "C++ это C с классами" и "C++ и C...   >>>>
Религия и goto (14.04.01)
   Начнем несколько издалека. В программировании существует тенденция к алгоритмизации самого процесса программирования. То есть, выведение некоторых универсальных правил, использование которых в...   >>>>
ploticus (16.10.00)
   Есть такая программа, предназначенная для создания графиков различных видов из командной строки, называется ploticus. Программа сама по себе достаточно удобная ---...   >>>>
Шаманство, или ошибки работы с памятью (25.09.00)
   Когда программа становится внушительной по своему содержанию (то есть, не по количеству строчек, а по непонятности внутренних связей), то ее поведение...   >>>>
Библиотека консорциума W3, libwww (20.09.00)
   Популярный нынче термин "веб-программирование" обычно подразумевает под собой программирование, в лучшем случае, на perl, в худшем --- на PHP, в совсем...   >>>>
Инварианты внутри программы (18.09.00)
   Вы когда-нибудь задумывались, над тем, как вы пишите программы? Если нет, то, я думаю, сегодняшняя заметка будет вам полезна. Итак, как...   >>>>
Содержание раздела полностью...
   Примерно в тоже время
По прозвищу "Пуля" / Bullet, 1994 (24.08.00)
   Фильмы с Микки Рурком практически не бывают "совсем" плохими --- мне кажется, что это один из лучших американских актеров... в этом...   >>>>
Java 2: руководство разработчика (22.08.00)
   Я решил, что мне нужна книга по Java тогда, когда появилось требование что-то написать на этом языке более серьезное, чем рисование...   >>>>
Хронология полностью...
   Содержание
Заглавная страница
Мой блог
Мое резюме
Дайджест
Программирование
  C&C++
Сети
Unix
Алгоритмы
Оптимизация
Соревнования
Отвлеченно
XML
TeX
Туризм
  Байки
Фотографии
Комментарии
  Книги
Web-ресурсы
Фильмы
Интернет
Программное обеспечение
Жизнь
Студенческое
Просто так
Благодарности
Форум
Хронология
© 2000-2008, Andrey L. Kalinin
mailto:andrey@kalinin.ru
Rambler's Top100