Рассмотрим пример: классический класс Shape
void draw_all(vector
{
for(int i = 0; i
}
Этот фрагмент кода действительно выглядит “довольно объектно-ориентированным”. Он основан на иерархии классов и вызове виртуальной функции, при котором правильная функция draw
Shape
находится автоматически; иначе говоря, для объекта класса Circle
он вызовет функцию Circle::draw
, а для объекта класса Open_polyline
— функцию Open_polyline::draw
. Однако класс vector
по существу является конструктивным элементом обобщенного программирования: он использует параметр (тип элемента), который выясняется на этапе компиляции. Следует подчеркнуть, что для итерации по всем элементам используется алгоритм из стандартной библиотеки.void draw_all(vector
{
for_each(v.begin,v.end,mem_fun(&Shape::draw));
}
Третьим аргументом функции for_each
f(x)
, а не функцию-член, вызываемую с помощью синтаксической конструкции p–>f
. Следовательно, для того чтобы указать, что на самом деле мы хотим вызвать функцию-член (виртуальную функцию Shape::draw
), необходимо использовать стандартную библиотечную функцию mem_fun
(раздел Б.6.2). Дело в том, что функции for_each
и mem_fun
, будучи шаблонными, на самом деле не очень хорошо соответствуют объектно-ориентированной парадигме; они полностью относятся к обобщенному программированию. Еще интереснее то, что функция mem_fun
является автономной (шаблонной) функцией, возвращающей объект класса. Другими словами, ее следует отнести к простой абстракции данных (нет наследования) или даже к процедурному программированию (нет сокрытия данных). Итак, мы можем констатировать, что всего лишь одна строка кода использует все четыре фундаментальных стиля программирования, поддерживаемых языком C++. for_each
for
, но для многих этот аргумент не является очень убедительным. Лучше сказать, что функция for_each
выражает то, что мы хотим сделать (пройти по последовательности), а не как мы это хотим сделать. Однако для большинства людей достаточно просто сказать: “Это полезно”. Такая запись демонстрирует путь обобщения (в лучших традициях обобщенного программирования), позволяющий устранить много проблем. Почему все фигуры хранятся в векторе, а не в списке или в обобщенной последовательности? Следовательно, мы можем написать третью (более общую) версию.template
{
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);
За неимением лучшего термина мы называем программирование, использующее смесь наиболее удобных стилей,
22.2. Обзор истории языков программирования
На заре человечества программисты высекали нули и единицы на камнях! Ну хорошо, мы немного преувеличили. В этом разделе мы вернемся к началу (почти) и кратко опишем основные вехи истории языков программирования в аспекте их связи с языком С++.