Обычно это делать нужно. На первый взгляд решение выглядит довольно просто:
void doSomethng() {
vector vwp;
... // Как прежде
for (vector::iterator = vwp.begin(); i != vwp.end(); ++i)
delete *i;
}
Такое решение работает, если не проявлять особой разборчивости в трактовке этого понятия. Во-первых, новый цикл for
делает примерно то же, что и for_each
, но он не столь нагляден (совет 43). Во-вторых, этот код небезопасен по отношению к исключениям. Если между заполнением vwp
указателями и вызовом delete
произойдет исключение, это снова приведет к утечке ресурсов. К счастью, с обеими проблемами можно справиться.
Чтобы от for_each
-подобного цикла перейти непосредственно к for_each
, необходимо преобразовать delete
в объект функции. С этим справится даже ребенок — если, конечно, вы найдете ребенка, который захочет возиться с STL:
template
struct DeleteObject: // В совете 40 показано,
public unary_function { // зачем нужно наследование
void operator()(const T* ptr) const {
delete ptr;
}
};
Теперь становится возможным следующее:
void doSomething() {
… //См. ранее
for_each(vwp.begin(), vwp.end(), DeleteObject());
}
К сожалению, вам приходится указывать тип объектов, удаляемых DeleteObject
(в данном примере Widget
), а это раздражает, vwp
представляет собой vector
— разумеется, DeleteObject
будет удалять указатели Widget*
! Подобные излишества не только раздражают, но и приводят к возникновению трудно обнаружимых ошибок. Допустим, кто-нибудь по случайности объявляет класс, производный от string
:
class SpecialString: public string{...};
Это рискованно, поскольку string
, как и все стандартные контейнеры STL, не имеет виртуального деструктора, а открытое наследование от классов без виртуального деструктора относится к числу основных табу C++. Подробности можно найти в любой хорошей книге по C++. (В «Effective C++» ищите в совете 14.) И все же некоторые программисты поступают подобным образом, поэтому давайте разберемся, как будет вести себя следующий код:
void doSomething() {
deque dssp;
for_each(dssp.begin(), end(), // Непредсказуемое поведение! Удаление
DeleteObject()); // производного объекта через указатель
// на базовый класс при отсутствии
// виртуального деструктора
}
Обратите внимание: dssp
объявляется как контейнер, в котором хранятся указатели SpecialString*
, но автор цикла for_each
сообщает DeleteObject
, что он будет удалять указатели string*
. Понятно, откуда берутся подобные ошибки. По своему поведению SpecialString
имеет много общего со string
, поэтому клиенту легко забыть, что вместо string
он использует SpecialString
.
Чтобы устранить ошибку (а также сократить объем работы для клиентов DeleteObject
), можно предоставить компилятору возможность вычислить тип указания, передаваемого DeleteObject::operator()
. Все, что для этого нужно, — переместить определение шаблона из DeleteObject
в operator()
:
struct DeleteObject{ // Убрали определение шаблона
// и базовый класс
template// Определение шаблона
void operator()(const T* ptr) const {
delete ptr;
}
};
Компилятор знает тип указателя, передаваемого DeleteObject::operator()
, поэтому мы можем заставить его автоматически создать экземпляр operator()
для этого типа указателя. Недостаток подобного способа вычисления типа заключается в том, что мы отказываемся от возможности сделать объект DeleteObject
адаптируемым (совет 40). Впрочем, если учесть, на какое применение он рассчитан, вряд ли это можно считать серьезным недостатком.
С новой версией DeleteObject
код клиентов SpecialString
выглядит так:
void doSomething() {
deque dssp;
...
for_each(dssp.begin(), dssp.end(),
DeleteObject());// Четко определенное поведение
}
Такое решение прямолинейно и безопасно по отношению к типам, что и требовалось.
Однако безопасность исключений все еще не достигнута. Если исключение произойдет после создания SpecialString
оператором new
, но перед вызовом for_each
, снова произойдет утечка ресурсов. Проблема решается разными способами, но простейший выход заключается в переходе от контейнера указателей к контейнеру умных указателей (обычно это указатели с подсчетом ссылок). Если вы незнакомы с концепцией умных указателей, обратитесь к любой книге по C++ для программистов среднего уровня и опытных. В книге «More Effective C++» этот материал приводится в совете 28.
Читать дальше