Чтение онлайн

на главную - закладки

Жанры

Эффективное использование STL
Шрифт:

delete ptr;

 }

};

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

void doSomething {

 … //См. ранее

 for_each(vwp.begin, vwp.end, DeleteObject<Widget>);

}

К сожалению, вам приходится указывать тип объектов, удаляемых

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

class SpecialString: public string{...};

Это рискованно, поскольку

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

void doSomething {

 deque<SpecialString*> dssp;

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

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

// на базовый класс при отсутствии

// виртуального деструктора

}

Обратите внимание:

dssp
объявляется как контейнер, в котором хранятся указатели
SpecialString*
, но автор цикла
for_each
сообщает
DeleteObject
, что он будет удалять указатели
string*
. Понятно, откуда берутся подобные ошибки. По своему поведению
SpecialString
имеет много общего со
string
, поэтому клиенту легко забыть, что вместо
string
он использует
SpecialString
.

Чтобы устранить ошибку (а также сократить объем работы для клиентов

DeleteObject
), можно предоставить компилятору возможность вычислить тип указания, передаваемого
DeleteObject::operator
. Все, что для этого нужно, — переместить определение шаблона из
DeleteObject
в
operator
:

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

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

 template<typename T> // Определение шаблона

 void operator(const T* ptr) const {

delete ptr;

 }

};

Компилятор знает тип указателя, передаваемого

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

С новой версией

DeleteObject
код клиентов
SpecialString
выглядит так:

void doSomething {

 deque<SpecialString*> dssp;

 ...

 for_each(dssp.begin, dssp.end,

DeleteObject);// Четко определенное поведение

}

Такое

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

Однако безопасность исключений все еще не достигнута. Если исключение произойдет после создания

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

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

К счастью, вам вряд ли придется создавать собственные умные указатели, поскольку найти проверенную реализацию не так сложно. Примером служит указатель

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

void doSomething {

 typedef boost::shared_ptr<Widget> SPW; //SPW = "shared pointer

// to Widget"

 vector<SPW> vwp;

 for (int i=0; i<SOME_MAGIC_NUMBER; ++i) //Создать SPW no Widget*

vwp.push_back(SPW(new Widget)); //и вызвать push_back

… //Использовать vwp

} //Утечки Widget не происходит.

//даже если в предыдущем фрагменте

//произойдет исключение

Никогда не следует полагать, что автоматическое удаление указателей можно обеспечить созданием контейнера, содержащего

auto_ptr
. Эта кошмарная мысль чревата такими неприятностями, что я посвятил ей совет 8.

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

shared_ptr
из библиотеки Boost), либо вручную удалить каждый указатель при уничтожении контейнера.

Напрашивается следующая мысль: если структура

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

Поделиться:
Популярные книги

Сержант. Назад в СССР. Книга 4

Гаусс Максим
4. Второй шанс
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Сержант. Назад в СССР. Книга 4

Ермак. Регент

Валериев Игорь
10. Ермак
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Ермак. Регент

Я уже князь. Книга XIX

Дрейк Сириус
19. Дорогой барон!
Фантастика:
юмористическое фэнтези
попаданцы
аниме
5.00
рейтинг книги
Я уже князь. Книга XIX

Инженер Петра Великого 2

Гросов Виктор
2. Инженер Петра Великого
Фантастика:
попаданцы
альтернативная история
фэнтези
5.00
рейтинг книги
Инженер Петра Великого 2

Последний Паладин. Том 14

Саваровский Роман
14. Путь Паладина
Фантастика:
аниме
фэнтези
попаданцы
5.75
рейтинг книги
Последний Паладин. Том 14

Дракон

Бубела Олег Николаевич
5. Совсем не герой
Фантастика:
фэнтези
попаданцы
9.31
рейтинг книги
Дракон

Туполев

Бодрихин Николай Георгиевич
1327. Жизнь замечательных людей
Документальная литература:
биографии и мемуары
5.00
рейтинг книги
Туполев

Печать мастера

Лисина Александра
6. Гибрид
Фантастика:
попаданцы
технофэнтези
аниме
фэнтези
6.00
рейтинг книги
Печать мастера

Третий Генерал: Том V

Зот Бакалавр
4. Третий Генерал
Фантастика:
городское фэнтези
аниме
сказочная фантастика
попаданцы
5.00
рейтинг книги
Третий Генерал: Том V

Ваше Сиятельство 7

Моури Эрли
7. Ваше Сиятельство
Фантастика:
боевая фантастика
аниме
5.00
рейтинг книги
Ваше Сиятельство 7

Я царь. Книга XXVIII

Дрейк Сириус
28. Дорогой барон!
Фантастика:
боевая фантастика
аниме
попаданцы
5.00
рейтинг книги
Я царь. Книга XXVIII

Девочка из прошлого

Тоцка Тала
3. Айдаровы
Любовные романы:
современные любовные романы
5.00
рейтинг книги
Девочка из прошлого

На границе империй. Том 4

INDIGO
4. Фортуна дама переменчивая
Фантастика:
космическая фантастика
6.00
рейтинг книги
На границе империй. Том 4

Бастард Императора. Том 15

Орлов Андрей Юрьевич
15. Бастард Императора
Фантастика:
городское фэнтези
аниме
фэнтези
фантастика: прочее
попаданцы
5.00
рейтинг книги
Бастард Императора. Том 15