Читаем Программирование. Принципы и практика использования C++ Исправленное издание полностью

  Итак, подведем итоги! Часто люди говорят о стилях программирования (парадигмах) так, будто они представляют собой противоречащие друг другу альтернативы: либо вы используете обобщенное программирование, либо объектно-ориентированное. Если хотите выразить решения задач наилучшим образом, то используйте комбинацию этих стилей. Выражение “наилучшим образом” означает, что вашу программу легко читать, писать, легко эксплуатировать и при этом она достаточно эффективна.

Рассмотрим пример: классический класс Shape, возникший в языке Simula (раздел 22.2.4), который обычно считается воплощением объектно-ориентированного программирования. Первое решение может выглядеть так:


void draw_all(vector& v)

{

  for(int i = 0; idraw;

}


Этот фрагмент кода действительно выглядит “довольно объектно-ориентированным”. Он основан на иерархии классов и вызове виртуальной функции, при котором правильная функция draw для каждого конкретного объекта класса Shape находится автоматически; иначе говоря, для объекта класса Circle он вызовет функцию Circle::draw, а для объекта класса Open_polyline — функцию Open_polyline::draw. Однако класс vector по существу является конструктивным элементом обобщенного программирования: он использует параметр (тип элемента), который выясняется на этапе компиляции. Следует подчеркнуть, что для итерации по всем элементам используется алгоритм из стандартной библиотеки.


void draw_all(vector& v)

{

  for_each(v.begin,v.end,mem_fun(&Shape::draw));

}


Третьим аргументом функции for_each является функция, которая должна вызываться для каждого элемента последовательности, заданной двумя первыми аргументами (раздел Б.5.1). Предполагается, что третья функция представляет собой обычную функцию (или функцию-объект), которая вызывается с помощью синтаксической конструкции f(x), а не функцию-член, вызываемую с помощью синтаксической конструкции p–>f. Следовательно, для того чтобы указать, что на самом деле мы хотим вызвать функцию-член (виртуальную функцию Shape::draw), необходимо использовать стандартную библиотечную функцию mem_fun (раздел Б.6.2). Дело в том, что функции for_each и mem_fun, будучи шаблонными, на самом деле не очень хорошо соответствуют объектно-ориентированной парадигме; они полностью относятся к обобщенному программированию. Еще интереснее то, что функция mem_fun является автономной (шаблонной) функцией, возвращающей объект класса. Другими словами, ее следует отнести к простой абстракции данных (нет наследования) или даже к процедурному программированию (нет сокрытия данных). Итак, мы можем констатировать, что всего лишь одна строка кода использует все четыре фундаментальных стиля программирования, поддерживаемых языком C++.

  Зачем же мы написали вторую версию примера для рисования всех фигур? По существу, она не отличается от первой, к тому же на несколько символов длиннее! В свое оправдание укажем, что выражение концепции цикла с помощью функции for_each является более очевидным и менее уязвимым для ошибок, чем цикл for, но для многих этот аргумент не является очень убедительным. Лучше сказать, что функция for_each выражает то, что мы хотим сделать (пройти по последовательности), а не как мы это хотим сделать. Однако для большинства людей достаточно просто сказать: “Это полезно”. Такая запись демонстрирует путь обобщения (в лучших традициях обобщенного программирования), позволяющий устранить много проблем. Почему все фигуры хранятся в векторе, а не в списке или в обобщенной последовательности? Следовательно, мы можем написать третью (более общую) версию.


template void draw_all(Iter b, Iter e)

{

  for_each(b,e,mem_fun(&Shape::draw));

}


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


Point p(0,100);

Point p2(50,50);

Shape* a[] = { new Circle(p,50), new Triangle(p,p2,Point(25,25)) };

draw_all(a,a+2);


За неимением лучшего термина мы называем программирование, использующее смесь наиболее удобных стилей, мультипарадигменным (multi-paradigm programming).

22.2. Обзор истории языков программирования

На заре человечества программисты высекали нули и единицы на камнях! Ну хорошо, мы немного преувеличили. В этом разделе мы вернемся к началу (почти) и кратко опишем основные вехи истории языков программирования в аспекте их связи с языком С++.

Перейти на страницу:
Нет соединения с сервером, попробуйте зайти чуть позже