Язык программирования C++ для профессионалов



           

Конкретные типы - часть 2


void my(slist& sl) { for (T* p = sl.first(); p; p = sl.next()) { // мой код } // ... }

void your(vector& v) { for (int i = 0; i<v.size(); i++) { // ваш код } // ... }

Существование таких "естественных" для выбранного метода реализации операций обеспечивает эффективность программы и значительно облегчает ее написание. К тому же, хотя реализация вызова подстановкой обычно возможна только для простых операций типа индексации массива или получения следующего элемента списка, она оказывает значительный эффект на скорость выполнения программы. Загвоздка здесь состоит в том, что фрагменты программы, использующие по своей сути эквивалентные операции, как, например, два приведенных выше цикла, могут выглядеть непохожими друг на друга, а фрагменты программы, в которых для эквивалентных операций используются разные конкретные типы, не могу заменять друг друга. Обычно, вообще, невозможно свести сходные фрагменты программы в один.

Пользователь, обращающийся к некоторой функции, должен точно указать тип объекта, с которым работает функция, например:

void user() { slist sl; vector v(100);

my(sl); your(v);

my(v); // ошибка: несоответствие типа your(sl); // ошибка: несоответствие типа }

Чтобы компенсировать жесткость этого требования, разработчик некоторой полезной функции должен предоставить несколько ее версий, чтобы у пользователя был выбор:

void my(slist&); void my(vector&);

void your(slist&); void your(vector&);

void user() { slist sl; vector v(100);

my(sl); your(v);

my(v); // теперь нормально: вызов my(vector&) your(sl); // теперь нормально: вызов your(slist&) }

Поскольку тело функции существенно зависит от типа ее параметра, надо написать каждую версию функций my() и your() независимо друг от друга, что может быть хлопотно.

С учетом всего изложенного конкретный тип, можно сказать, походит на встроенные типы. Положительной стороной этого является тесная связь между пользователем типа и его создателем, а также между пользователями, которые создают объекты данного типа, и пользователями, которые пишут функции, работающие с этими объектами. Чтобы правильно использовать конкретный тип, пользователь должен разбираться в нем детально. Обычно не существует каких-то универсальных свойств, которыми обладали бы все конкретные типы библиотеки, и что позволило бы пользователю, рассчитывая на эти свойства, не тратить силы на изучение отдельных классов. Такова плата за компактность программы и эффективность ее выполнения. Иногда это вполне разумная плата, иногда нет. Кроме того, возможен такой случай, когда отдельный конкретный класс проще понять и использовать, чем более общий (абстрактный) класс. Именно так бывает с классами, представляющими хорошо известные типы данных, такие как массивы или списки.

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




Содержание  Назад  Вперед