Функциональный деструктор в С++

В С++ крайне распространен прием использования класса 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 вам придется переписать сам цикл. Хорошо, когда такое место одно в программе, а если их тысячи?


Оригинальный пост | Disclaimer

Комментарии