Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14 - [57]

Шрифт
Интервал

>auto pw = new Widget;   // pw - обычный указатель

>…

>std::shared_ptr

> spw1(pw, loggingDel);  // Создание управляющего блока для *pw

>…

>std::shared_ptr // Создание второго

> spw2(pw, loggingDel);  // управляющего блока для *pw!

Создание обычного указателя >pw, указывающего на динамически выделенный объект, — плохое решение, поскольку оно противоречит главному совету всей главы: предпочитайте интеллектуальные указатели обычным указателям. (Если вы забыли, откуда взялся этот совет, заново прочтите первую страницу данной главы.) Но пока что забудем об этом. Строка, в которой создается >pw, представляет собой стилистическую мерзость, но она по крайней мере не приводит к неопределенному поведению.

Далее вызывается конструктор для >spw1, которому передается обычный указатель, так что этот конструктор создает управляющий блок (и тем самым счетчик ссылок) для того, на что он указывает. В данном случае это >*pw (т.e. объект, на который указывает >pw). Само по себе это не является чем-то страшным, но далее с тем же обычным указателем вызывается конструктор для >spw2, и он также создает управляющий блок (а следовательно, и счетчик ссылок) для >*pw. Объект >*pw, таким образом, имеет два счетчика ссылок, каждый из которых в конечном итоге примет нулевое значение, и это обязательно приведет к попытке уничтожить объект >*pw дважды. Второе уничтожение и будет ответственно за неопределенное поведение.

Из этого можно вынести как минимум два урока, касающиеся применения >std::shared_ptr. Во-первых, пытайтесь избегать передачи обычных указателей конструктору >std::shared_ptr. Обычной альтернативой этому является применение функции >std::make_shared (см. раздел 4.4), но в примере выше мы использовали пользовательские удалители, а это невозможно при использовании >std::make_shared. Во-вторых, если вы вынуждены передавать обычный указатель конструктору >std::shared_ptr, передавайте непосредственно результат оператора >new, а не используйте переменную в качестве посредника. Если первую часть кода переписать следующим образом,

>std::shared_ptr

> spw1(new Widget, // Непосредственное использование new

>      loggingDel);

то окажется гораздо труднее создать второй >std::shared_ptr из того же обычного указателя. Вместо этого автор кода, создающего >spw2, естественно, будет использовать в качестве аргумента инициализации >spw1 (т.e. будет вызывать копирующий конструктор >std::shared_ptr), и это не будет вести ни к каким проблемам:

>std::shared_ptr // spw2 использует тот же

> spw2(spw1);            // управляющий блок, что и spw1

Особенно удивительный способ, которым применение переменных с обычными указателями в качестве аргументов конструкторов >std::shared_ptr может привести к множественным управляющим блокам, включает указатель >this. Предположим, что наша программа использует указатели >std::shared_ptr для управления объектами >Widget и у нас есть структура данных, которая отслеживает объекты >Widget, которые были обработаны:

>std::vector<std::shared_ptr> processedWidgets;

Далее, предположим, что >Widget имеет функцию-член, выполняющую эту обработку:

>class Widget {

>public:

> …

> void process();

>};

Вот вполне разумно выглядящий подход к написанию >Widget::process:

>void Widget::process() {

> …                                    // Обработка Widget

> processedWidgets.emplace_back(this); // Добавление в список

>}                                     // обработанных Widget;

>                                      // это неправильно!

Комментарий о неправильности кода относится не к использованию >emplace_back, а к передаче >this в качестве аргумента. (Если вы не знакомы с >emplace_back, обратитесь к разделу 8.2.) Этот код будет компилироваться, но он передает обычный указатель (>this) контейнеру указателей >std::shared_ptr. Конструируемый таким образом указатель >std::shared_ptr будет создавать новый управляющий блок для указываемого >Widget (т.e. для объекта >*this). Это не выглядит ужасным до тех пор, пока вы не понимаете, что, если имеются указатели >std::shared_ptr вне функции-члена, которые уже указывают на этот объект >Widget, вам не избежать неопределенного поведения.

API >std::shared_ptr включает средство для ситуаций такого рода. Вероятно, его имя — наиболее странное среди всех имен стандартной библиотеки: >std::enable_shared_from_this. Это шаблон базового класса, который вы наследуете, если хотите, чтобы класс, управляемый указателями >std::shared_ptr, был способен безопасно создавать >std::shared_ptr из указателя >this. В нашем примере >Widget будет унаследован от >std::enable_shared_from_this следующим образом:

>class Widget: public std::enable_shared_from {

>public:

> …

> void process();


>};

Как я уже говорил, >std::enable_shared_from_this является шаблоном базового класса. Его параметром типа всегда является имя производного класса, так что класс >Widget порождается от >std::enable_shared_from_this. Если идея производного класса, порожденного от базового класса, шаблонизированного производным, вызывает у вас головную боль, попытайтесь об этом не думать. Этот код совершенно законный, а соответствующий шаблон проектирования хорошо известен, имеет стандартное имя, хотя и почти такое же странное, как


Еще от автора Скотт Мейерс
Эффективное использование STL

В этой книге известный автор Скотт Мейерс раскрывает секреты настоящих мастеров, позволяющие добиться максимальной эффективности при работе с библиотекой STL.Во многих книгах описываются возможности STL, но только в этой рассказано о том, как работать с этой библиотекой. Каждый из 50 советов книги подкреплен анализом и убедительными примерами, поэтому читатель не только узнает, как решать ту или иную задачу, но и когда следует выбирать то или иное решение — и почему именно такое.


Как функции, не являющиеся методами, улучшают инкапсуляцию

Когда приходится инкапсулировать, то иногда лучше меньше, чем большеЯ начну со следующего утверждения: Если вы пишете функцию, которая может быть выполнена или как метод класса, или быть внешней по отношению к классу, Вы должны предпочесть ее реализацию без использования метода. Такое решение увеличивает инкапсуляцию класса. Когда Вы думаете об использовании инкапсуляции, Вы должны думать том, чтобы не использовать методы.Удивлены? Читайте дальше.


Рекомендуем почитать
Изучаем Java EE 7

Java Enterprise Edition (Java EE) остается одной из ведущих технологий и платформ на основе Java. Данная книга представляет собой логичное пошаговое руководство, в котором подробно описаны многие спецификации и эталонные реализации Java EE 7. Работа с ними продемонстрирована на практических примерах. В этом фундаментальном издании также используется новейшая версия инструмента GlassFish, предназначенного для развертывания и администрирования примеров кода. Книга написана ведущим специалистом по обработке запросов на спецификацию Java EE, членом наблюдательного совета организации Java Community Process (JCP)


DirectX 8. Начинаем работу с DirectX Graphics

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


Платформа J2Me

Эта книга научит вас, как разрабатывать программное обеспечение для платформы J2ME компании «Sun Microsystems». Эта книга придерживается стиля учебного пособия, это не справочное руководство.Цель — дать вам твердую основу в понятиях и техниках, которая даст вам возможность решиться на самостоятельную разработку качественных приложений.


Обработка баз данных на Visual Basic.NET

Это практическое руководство разработчика программного обеспечения на Visual Basic .NET и ADO.NET, предназначенное для создания приложений баз данных на основе WinForms, Web-форм и Web-служб. В книге описываются практические способы решения задач доступа к данным, с которыми сталкиваются разработчики на Visual Basic .NET в своей повседневной деятельности. Книга начинается с основных сведений о создании баз данных, использовании языка структурированных запросов SQL и системы управления базами данных Microsoft SQL Server 2000.


Исчерпывающее руководство по написанию всплывающих подсказок

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


Программное обеспечение встроенных систем. Общие требования к разработке и документированию

Embedded system software. General requirements for development and documentationСтандарт подготовлен в развитие ГОСТ Р ИСО/МЭК 12207-99 «Информационная технология. Процессы жизненного цикла программных средств» с целью учета специфики разработки и документирования программного обеспечения встроенных систем реального времени.