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

ЖАНРЫ

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

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

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
.

Совет 8. Никогда не создавайте контейнеры, содержащие auto_ptr

Честно говоря, в книге, посвященной эффективному использованию STL, данный совет не совсем уместен. Контейнеры

auto_ptr
(COAP, Containers Of Auto_Ptr) запрещены, а программа, которая попытается их использовать, не будет компилироваться. Комитет по стандартизации C++ приложил неслыханные усилия в этом направлении. Возможно, мне вообще не стоило бы говорить о контейнерах
auto_ ptr
— о них вам расскажет компилятор, причем в самых нелестных выражениях.

Однако многие программисты работают на платформах STL, на которых COAP не запрещены. Более того, многие программисты по-прежнему подвержены иллюзии и видят в COAP простое, прямолинейное, эффективное средство для борьбы с утечкой ресурсов, часто присущей контейнерам указателей (советы 7 и 33). В результате возникает искушение воспользоваться COAP, даже если их невозможно создать.

Вскоре я объясню, почему COAP произвели такой переполох, что Комитет по стандартизации предпринял специальные шаги по их запрещению. А пока начнем с первого недостатка, для понимания которого не нужно разбираться в

auto_ptr
и вообще в контейнерах: COAP не переносимы. Да и как может быть иначе? Они запрещены стандартом C++, и наиболее передовые платформы STL уже выполняют это требование. Вероятно, со временем платформы STL, которые сейчас не соответствуют Стандарту, выполнят его требования. Когда это произойдет, программы, использующие COAP, станут еще менее переносимыми, чем сейчас. Тот, кто заботится о переносимости своих программ, отвергнет COAP хотя бы по этой причине.

Впрочем, не исключено, что переносимость вас не волнует. Если это так, позвольте напомнить об уникальном (а по мнению некоторых — нелепом) смысле операции копирования

auto_ptr
.

При копировании

auto_ptr
право владения объектом, на который ссылается указатель, переходит к копии, а исходному указателю присваивается NULL. Да, вы не ошиблись: копирование указателя auto_ptr приводит к его модификации.

auto_ptr<Widget> pw1(new Widget); //pw1 ссылается на Widget

auto_ptr<Widget> pw2(pw1); //pw2 ссылается на объект Widget,

//принадлежащий pw1; pw1 присваивается

//NULL (таким образом, объект Widget

//передается от pw1 к pw2)

pwl = pw2; //pw1 снова ссылается на Widget:

//pw2 присваивается NULL

Конечно, такое поведение необычно и даже по-своему интересно, но для пользователя STL в первую очередь важно то, что оно приводит к крайне неожиданным последствиям. Рассмотрим внешне безобидный фрагмент, который создает вектор

auto_ptr<Widget>
и сортирует его функцией, сравнивающей значения Widget:

Поделиться с друзьями: