В С++ крайне распространен прием использования класса std::vector
для хранения указателей на размещенные в куче объекты.
class Book { public: Book(int index); ... }; ... std::vector<Book *> books; for (int i = 0; i < 10; ++i) books.push_back(new Book(i));
Естественно, после использования память надо освободить. Обычно стандартный прием для этого таков:
for (std::vector<Book *>::iterator i = books.begin(); i != books.end(); ++i) delete *i;
В целом, с таким подходом все в порядке, разве что слегка веет от него излишней алгоритмической загруженностью. Он вынужден, навязан особенностями языка C++. Индексная переменная i
здесь абсолютно неважна для цикла, она является чисто служебной. Все это, конечно, не так страшно, как использование оператора goto или статических переменных, но все равно хочется гармонии. И способ есть. Данный код можно переписать так:
#include <algorithm> class deleter { public: template <typename T> void operator()(const T* p) const { delete p; } }; ... std::for_each(a.begin(), a.end(), deleter());
Данный код определяет класс-функтор, у которого перегруженный оператор operator()
является шаблонным. Затем стандартный алгоритм std::for_each()
вызывает этот оператор для каждого члена вектора.
Конечно, вы можете сказать, что мол битва за идею принуждает нас таскать за собой класс deleter
, но тут аргумент простой — данный подход ближе к декларативному подходу в программировании, нежели к прямому алгоритмическому. В декларативном подходе вы стараетесь как можно больше логики перенести из ее явного программирования базовыми конструкциями типа условий, циклов и т.д. к ее выражению через определения (декларации) сущностей и их взаимосвязей. Декларативные конструкции проще дробить на независимые куски, а значит проще тестировать. Например, вы можете протестировать алгоритм std::for_each
в изоляции, тем самым гарантируя его корректную работу сразу во всей программе, а вот протестировать явный цикл в изоляции вряд ли получится, так как цикл “жестко вплетен” в прочую логику программы. Максимум удастся проверить данный конкретный цикл как-то вручную, и если их программе много, проверять придется каждый из них.
Соглашусь, однако, что конкретно этот пример весьма тривиален и является в большинстве делом вкуса, нежели вопросом реального выигрыша простоте и тестируемости кода. Но сам прием весьма показателен в плане замены простейших алгоритмов высокоуровневыми сущностями. И еще, в защиту такого приему могу сказать, что например, вы можете переопределить алгоритм std::for_each
на свой, который сможет на конкретно вашей платформе выполняться гораздо быстрее, или, например, ловить исключения работы с кучей и журналировать проблемы освобождения памяти. В случае же прямого использования цикла for
вам придется переписать сам цикл. Хорошо, когда такое место одно в программе, а если их тысячи?