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

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

Но есть и дополнительный стимул для применения >noexcept к функциям, которые не генерируют исключений: это позволяет компиляторам генерировать лучший объектный код. Чтобы понять, почему это так, рассмотрим разницу между способами, которыми С++98 и С++11 сообщают о том, что функция не может генерировать исключения. Пусть имеется функция >f, которая обещает вызывающему коду, что тот никогда не получит исключения. Вот как выглядят два способа это выразить:

>int f(int x) throw();  // f не генерирует исключений: С++98

>int f(int x) noexcept; // f не генерирует исключений: C++11

Если во время выполнения некоторое исключение покинет >f, тем самым будет нарушена спецификация исключений >f. При спецификации исключений С++98 стек вызовов сворачивается[6] до вызывающего >f кода, и после некоторых действий, не имеющих значения для данного рассмотрения, выполнение программы прекращается. При спецификации исключений С++11 поведение времени выполнения несколько иное: стек только, возможно, сворачивается перед завершением выполнения программы.

Разница между сворачиванием стека и возможным сворачиванием оказывает на удивление большое влияние на генерацию кода. В случае функции, объявленной как >noexcept, оптимизаторам не надо ни поддерживать стек в сворачиваемом состоянии, ни гарантировать, что объекты в такой функции будут уничтожены в порядке, обратном созданию, если вдруг такую функцию покинет исключение. Функции со спецификацией >throw() не имеют такой гибкости оптимизации, как и функции без спецификаций вообще. Ситуацию можно резюмировать следующим образом:

>RetType function(params) noexcept; // Наиболее оптимизируема

>RetType function(params) throw();  // Менее оптимизируема

>RetType function(params);          // Менее оптимизируема

Этого одного достаточно для того, чтобы объявлять функции, о которых точно известно, что они не генерируют исключений, как >noexcept.

Для некоторых функций все оказывается еще более интересным. Выдающимся примером являются операции перемещения. Предположим, что у вас имеется код С++98, использующий >std::vector. Объекты типа >Widget время от времени добавляются в >std::vector  с помощью функции >push_back:

>std::vector vw;

>…

>Widget w;

>…                // Работа с w

>vw.push_back(w); // Добавление w к vw

Предположим, что этот код отлично работает, и нет никакой необходимости изменять его для С++11. Однако вы хотите воспользоваться тем фактом, что семантика перемещения С++11 может улучшить производительность старого кода при участии типов, допускающих перемещающие операции. Вы уверены, что класс >Widget такие операции имеет — либо потому, что вы написали их самостоятельно, либо потому, что вы убедились в осуществлении условий для их автоматической генерации (см. раздел 3.11).

Когда в >std::vector добавляется новый элемент, может оказаться, что в >std::vector для него не хватает места, т.e. что размер >std::vector равен его емкости. Когда такое случается, >std::vector выделяет новый, больший по размеру блок памяти для хранения своих элементов и переносит элементы из старого блока в новый. В С++98 перенос осуществляется с помощью копирования каждого элемента из старой памяти в новую с последующим удалением объекта в старой памяти. Этот подход позволяет >push_back обеспечить строгую гарантию безопасности исключений: если исключение будет сгенерировано в процессе копирования элементов, то состояние >std::vector останется неизменным, поскольку ни один элемент в старом блоке памяти не будет удален, пока все элементы не будут успешно скопированы в новое место в памяти.

В С++11 естественной оптимизацией была бы замена копирования элементов >std::vector перемещениями. К сожалению, это ведет к риску нарушения строгой гарантии >push_back. Если >n элементов перемещены из старого блока памяти в новый и при перемещении >n+1-го элемента генерируется исключение, операция >push_back не может быть завершена. Но при этом исходный >std::vector находится в измененном состоянии: >n его элементов уже перемещены. Восстановление их исходных состояний может оказаться невозможным, поскольку попытка перемещения каждого объекта обратно в исходное местоположение в памяти также может привести к генерации исключения.

Это серьезная проблема, поскольку поведение старого кода может зависеть от строгой гарантии безопасности функции >push_back. Следовательно, реализации С++11 не могут молча заменить операции копирования внутри >push_back операциями перемещения, если только точно не известно, что операции перемещения не генерируют исключений. В таком случае замена копирований перемещениями должна быть безопасной, и единственным побочным эффектом этой замены будет повышение быстродействия.

Функция >std::vector::push_back использует преимущество этой стратегии (“перемести, если можешь, но копируй, если должен”), и это не единственная функция стандартной библиотеки, поступающая таким образом. Другие функции, обеспечивающие строгую гарантию безопасности исключений в С++98 (например, >std::vector::reserve, >std::deque::insert и др.), ведут себя точно так же. Все эти функции заменяют вызовы копирующих операций в С++98 вызовами перемещающих операций в С++11, только если известно, что перемещающие операции не генерируют исключений. Но как функция может узнать, генерирует ли исключения операция перемещения? Ответ очевиден: она должна проверить, объявлена ли операция как


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

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


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

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


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

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


Платформа J2Me

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


Виртуальная библиотека Delphi

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


Обработка баз данных на 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 «Информационная технология. Процессы жизненного цикла программных средств» с целью учета специфики разработки и документирования программного обеспечения встроенных систем реального времени.