Программирование >>  Включение нужных заголовков 

1 ... 8 9 10 [ 11 ] 12 13 14 ... 71


Все элементы vwp уничтожаются при выходе vwp из области видимости, но это не изменяет того факта, что delete не вызывается для объектов, созданных оператором new. За удаление таких элементов отвечает программист, а не контейнер. Так было задумано. Только программист знает, нужно ли вызывать del ete для этих указателей.

Обычно это делать нужно. На первый взгляд решение выглядит довольно просто:

void doSomethingО {

vector<Widget*> vwp:

Как прежде for (vector<Widget*>::iterator i=vwp.begin(); i!=vwp.end(): ++i)

delete *i:

Такое решение работает, если не проявлять особой разборчивости в трактовке этого понятия. Во-первых, новый цикл for делает примерно то же, что и foreach, но он не столь нагляден (совет 43). Во-вторых, этот код небезопасен по отношению к исключениям. Если между заполнением vwp указателями и вызовом delete произойдет исключение, это снова приведет к утечке ресурсов. К счастью, с обеими проблемами можно справиться.

Чтобы от ГогеасИ-подобного цикла перейти непосредственно к foreach, необходимо преобразовать delete в объект функции. С этим справится даже ребенок - если, конечно, вы найдете ребенка, который захочет возиться с STL:

template <typename Т>

struct DeleteObject: В совете 40 показано,

public unary function<const T*.void> { зачем нужно наследование

void operatorO(const Т* ptr) const {

delete ptr:

Теперь становится возможным следующее:

void doSomethingО {

См. ранее

for each(vwp.begi n(),vwp.endO,DeleteObject<Widget>());

К сожалению, вам приходится указывать тип объектов, удаляемых DeleteObject (в данном примере Wi dget), а это раздражает, vwp представляет собой vector Wi dget*> - разумеется, DeleteObject будет удалять указатели Widget*! Подобные излишества не только раздражают, но и приводят к возникновению трудно обнаружимых ошибок. Допустим, кто-нибудь по случайности объявляет класс, производный от string:

class SpecialString: public string{...}:

Это рискованно, поскольку string, как и все стандартные контейнеры STL, не имеет виртуального деструктора, а открытое наследование от классов без виртуального деструктора относится к числу основных табу C-i-i-. Подробности можно



Компилятор знает тип указателя, передаваемого Del eteOb ject:: operator О, поэтому мы можем заставить его автоматически создать экземпляр operatorO для этого типа указателя. Недостаток подобного способа вычисления типа заключается в том, что мы отказываемся от возможности сделать объект Del eteObject адаптируемым (совет 40). Впрочем, если учесть, на какое применение он рассчитан, вряд ли это можно считать серьезным недостатком.

С новой версией Del eteObject код клиентов Special String выглядит так:

void doSomethingO {

deque<SpecialString*> dssp:

for each(dssp.begi n(),dssp.end(),

DeleteObjectO): Четко определенное поведение

Такое решение прямолинейно и безопасно по отношению к типам, что и требовалось.

Однако безопасность исключений все еще не достигнута. Если исключение произойдет после создания Special String оператором new, но перед вызовом foreach.

найти в любой хорошей книге по С++. (В Effective С++ ищите в совете 14.) И все же некоторые программисты поступают подобным образом, поэтому давайте разберемся, как будет вести себя следующий код:

void doSomethingO {

deque<SpecialString*> dssp:

for each(dssp.begin(),end(), Непредсказуемое поведение! Удаление

DeleteObject<string>()): производного объекта через указатель

на базовый класс при отсутствии виртуального деструктора

Обратите внимание: dssp объявляется как контейнер, в котором хранятся указатели Special String*, но автор цикла for each сообщает Del eteOb ject, что он будет удалять указатели string*. Понятно, откуда берутся подобные ошибки. По своему поведению Special String имеет много общего со string, поэтому клиенту легко забыть, что вместо string он использует Special String.

Чтобы устранить ошибку (а также сократить объем работы для клиентов Del eteObject), можно предоставить компилятору возможность вычислить тип указания, передаваемого DeleteObject: :operator(). Все, что для этого нужно, - переместить определение шаблона из Del eteOb ject в operator О:

struct DeleteObject{ Убрали определение шаблона

и базовый класс

tempi ate<typenanie Т> Определение шаблона

void operatorO(const Т* ptr) const

delete ptr:



vector<SPW> vwp:

to Widget

for (int i=0;i<SOME MAGIC NUMBER:++i) Создать SPW no Widget* vwp.push back(SPW(new Widget)): и вызвать push back

Использовать vwp } Утечки Wodget не происходит.

даже если в предыдущем фрагменте произойдет исключение

Никогда не следует полагать, что автоматическое удаление указателей можно обеспечить созданием контейнера, содержащего auto ptr. Эта кошмарная мысль чревата такими неприятностями, что я посвятил ей совет 8.

Главное, что необходимо запомнить: контейнеры STL разумны, но они не смогут решить, нужно ли удалять хранящиеся в них указатели. Чтобы избежать утечки ресурсов при работе с контейнерами указателей, необходимо либо воспользоваться объектами умных указателей с подсчетом ссылок (такими, как shared ptr из библиотеки Boost), либо врзную удалить каждый указатель при уничтожении контейнера.

Напрашивается следующая мысль: если структура Del eteOb ject помогает справиться с утечкой ресурсов для контейнеров, содержащих указатели на объекты, можно создать аналогичную структуру DeleteArray, которая поможет избежать утечки ресурсов для контейнеров с указателями на массивы. Конечно, такое решение возможно. Другой вопрос, насколько оно разумно. В совете 13 показано, почему динамически размещаемые массивы почти всегда уступают vector и string.

снова произойдет утечка ресурсов. Проблема решается разными способами, но простейший выход заключается в переходе от контейнера указателей к контейнеру умных указателей (обычно это указатели с подсчетом ссылок). Если вы незнакомы с концепцией умных указателей, обратитесь к любой книге по С++ для программистов среднего уровня и опытных. В книге Моге Effective С++ этот материал приводится в совете 28.

Библиотека STL не содержит умных указателей с подсчетом ссылок. Написание хорошего умного указателя (то есть такого, который бы всегда правильно работал) - задача не из простых, и заниматься ею стоит лишь в случае крайней необходимости. Я привел код умного указателя с подсчетом ссылок в Моге Effective С++ в 1996 году. Хотя код был основан на хорошо известной реализации умного указателя, а перед изданием книги материал тщательно проверялся опытными программистами, за эти годы было найдено несколько ошибок. Количество нетривиальных сбоев, возникающих при подсчете ссылок в умных указателях, просто невероятно (за подробностями обращайтесь к списку опечаток и исправлений для книги Моге Effective С++ [28]).

К счастью, вам вряд ли придется создавать собственные умные указатели, поскольку найти проверенную реализацию не так сложно. Примером служит указатель siiared ptr из библиотеки Boost (совет 50). Используя shared ptr, можно записать исходный пример данного совета в следующем виде:

void doSomethingО {

typedef boost::shared ptr<Widget> SPW; SPW = shared pointer



1 ... 8 9 10 [ 11 ] 12 13 14 ... 71

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика