Полиморфизм и указатели на функции

Читая учебники по программированию, убеждаюсь в том, как сильно усложняется восприятие материала, если автор подчиняет себя концепциям и инструментам, забывая о том, насколько важен контекст, в котором раскрывается материал. И, в то же время, насколько естественно и легко приходит понимание, когда ощущаешь реальное удобство от применения нового инструмента.

На мой взгляд, указатели на функции является как раз таким примером. Дело в том, что синтаксис объявления и использования указателей на функции не слишком очевиден, особенно для не очень опытных программистов, и, если сразу «сыпать» деталями и синтаксическими возможностями, то за деревьями не будет видно леса. В этом посте я хочу поделиться своим видением указателей на функции и привести простой пример, который, смею надеяться, порадует своей легкостью и убьет еще одного зайца — продемонстрирует страшного монстра под названием «полиморфный вызов».

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

const int n = 15;
string cities[n] = {
	"Красноярск", "Москва", "Новосибирск", "Лондон", "Токио", 
	"Пекин", "Волгоград", "Минск", "Владивосток", "Париж", 
	"Манчестер", "Вашингтон", "Якутск", "Екатеринбург", "Омск"
};

Пусть нам требуется иметь возможность сортировки по алфавиту (вверх/вниз) и по длине строки (вверх/вниз). В других случаях это может быть по росту, по весу, по населению, по зарплате. Конечно, мы могли бы реализовать какой-нибудь метод сортировки и оттиражировать его с изменением способа сравнения. Но, как мы понимаем, возникает дублирование кода со всеми вытекающими из него последствиями.

А как же иначе быть, если мы не можем передать функцию в качестве аргумента в функцию сортировки? Или, все-таки, можем? Здесь и приходят на помощь указатели на функции. Это как раз то средство, которое позволяет работать с функциями так же, как и с переменными. Ну, или почти так же.

Итак, вот он указатель на функцию. Встречайте:

typedef bool(*CompareFunction) (int i, int j);

Не пытайтесь разобраться в синтаксисе. Просто знайте, что после этой команды мы можем использовать тип CompareFunction, чтобы объявлять переменные-функции, правда с фиксированной сигнатурой. В нашем случае это должны быть логические функции с двумя целочисленными аргументами. Как вы могли догадаться, это будут различные способы сравнения. Вот и они:

bool compareUp(int i, int j) {
	return cities[i] < cities[j];
}

bool compareDown(int i, int j) {
	return cities[i] > cities[j];
}

bool compareLengthUp(int i, int j) {
	return cities[i].length() < cities[j].length();
}

bool compareLengthDown(int i, int j) {
	return cities[i].length() > cities[j].length();
} 

Теперь помещаем их в массив:

CompareFunction compares[] = {compareUp, compareDown, compareLengthUp, compareLengthDown};

А затем создаем функцию сортировки:

void sortCities(CompareFunction compare) {
	for (int i = 0; i < n - 1; i++) {
		int j_min = i;
		for (int j = i+1; j < n; j++) {
			if (<b>compare(j, j_min)</b>) {
				j_min = j;
			}
		}
		string temp = cities[i];
		cities[i] = cities[j_min];
		cities[j_min] = temp;
	}
}

А что же там выделено жирным? Это и есть полиморфный вызов: переменной compare можно подсунуть любую функцию сравнения, и вызываемая функция будет определяться динамически, обеспечивая требуемый способ сортировки.

И, наконец, нужен какой-нибудь простенький user interface, чтобы все протестировать.

int _tmain(int argc, _TCHAR* argv[]) {
	setlocale(LC_ALL, "Russian");
	
	for (;;) {
		int choice;
		cout << "Ваш выбор: ";
		cin >> choice;
		sortCities(compares[choice]);
		printCities();
	}
	system("pause");
	return 0;
}

Теперь все прекрасно. Один программист штампует логические функции, второй — оптимизирует алгоритм сортировки, а третий — работает над юзабельностью пользовательского интерфейса. И никто никому не мешает — даже дублирование кода.

P.S. Функция printCities() виртуальная, ее реализация выпадает на долю читателя.
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 16

    +1
    Очень часто материал подается с точки зрения «ух ты посмотри как тут много винтиков, гаек, планок и отвертка с ключом!», но не приводится примера задачи куда это применить можно. Многие думают, что полиморфизм это свойство исключительно ООП.
      0
      С указателями разобрались, с пузырьком тоже, но чем вам std::sort не угодил?

      Кстати, лайфхак: если неохота читать и даже писать страшный тайпдефы:
      template <typename BinaryFunction>
      void sort(BinaryFunction comparator) { ... };
      // ...
      sort( [](int a, int b) { return true; /* ... */ } );
      
        +3
        Мне кажется, что из текста достаточно ясно, что цель статьи — это не сделать дубликат std::sort, а проиллюстрировать концепцию. То же самое можно было бы продемонстрировать на многих других алгоритмах.
          0
          Как по мне, эту концепцию отлично иллюстрируют custom deter-ы:
          using AutoFile   = std::unique_ptr<FILE, decltype(&fclose)>;
          const auto openFileInC = [](const std::string& name) { return AutoFile(fopen(name.c_str(), "w"), fclose); };
          
          using AutoHandle = std::unique_ptr<void, decltype(&CloseHandle)>;
          const auto openFileInApi = [](const std::string& name) 
          { 
          	return AutoHandle(::CreateFileA(name.c_str(), FILE_ALL_ACCESS, 0, nullptr, CREATE_ALWAYS, 0, nullptr), CloseHandle); 
          };
          
          int main()
          {
          	// RAII wrapper will auto close descriptor
          	auto fileInC = openFileInC("1.txt");
          	fprintf(fileInC.get(), "Hello");
          
          	// RAII wrapper will auto close handle
          	auto fileInApi = openFileInApi("2.txt");
          	WriteFile(fileInApi.get(), "World", 5, nullptr, nullptr);
          
          	return 0;
          }
          
          0

          К слову это не пузырек, а сортировка выбором.

          +1
          А это точно про полиморфизм? Вижу тут только функциональную переменную. Полиморфная функция должна сама переключаться на нужную реализацию в зависимости от параметра, а вы тут выбор руками делаете.
            –3
            В контексте функции sortCities(CompareFunction compare) так и делается. Передается параметр и переключается на нужную реализацию. А ручной выбор в main() — это уже следующий слой.
              +2
              Нет, в общепринятом смысле полиморфное поведение управляется типом самих данных, а не дополнительным параметром.
                0
                Ну, это же не проблема. Давайте весь код (кроме main) завернем в какой-нибудь класс и будет то, о чем Вы говорите. Смысл-то абсолютно тот же, что в полиморфизме.
                Идея полиморфизма же заключается не в том, чтобы тип чем-то управлял, а в том, что одна и та же строка кода работала по-разному в зависимости от того, что туда «подложат». Перегрузка операторов — это тоже вид полиморфизма.
                  0
                  Я думаю автор имел в виду subtype polymorphism в вариации для функций первого класса. Вы же говорите про ad hoc polymorphism.
                    0
                    Действительно. Спасибо за замечание, я не придавал внимания этой классификации.
              +2
              «В языках программирования и теории типов полиморфизмом называется способность функции обрабатывать данные разных типов»
              В упор не вижу полиморфизма. Скорее тут можно говорить про паттерн «Стратегия».
                0
                Конечно, это и есть паттерн «стратегия» (в «не-ООП» варианте), который и основан на полиморфном вызове функции (метода).
                0
                А можно увидеть вариант с кастомной сортировкой произвольного массива, а то пример получается слишком далёким от реальности.
                  0

                  Может я несколько выпал из времени, но, например, qsort так работает испокон веков...

                    –1
                    Для белого пояса самое то, спасибо!

                    Only users with full accounts can post comments. Log in, please.