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

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

:

>class Polynomial {

>public:

> using RootsType =     // Структура данных, хранящая

>  std::vector; // значения, где полином равен нулю

>  …                    // (см. "using" в разделе 3.3)

> RootsType roots() const;


>};

Вычисление корней — трудная дорогостоящая операция, так что мы не хотим их вычислять до того, как они реально потребуются. Но если они нам требуются, то, определенно, требуются не один раз. Поэтому мы будем кешировать корни полиномов, если нам приходится их вычислять, и реализуем >roots так, чтобы функция возвращала кешированное значение. Вот как выглядит такой подход:

>class Polynomial {

>public:

> using RootsType = std::vector;


> RootsType roots() const {

>  if (!rootsAreValid) { // Если кеш некорректен,

>   …                    // вычисляем корни и сохраняем

>                        // их в in rootVals

>   rootsAreValid = true;

> }

> return rootVals;

>}

>private:

> mutable bool rootsAreValid{ false }; // См. инициализаторы

> mutable RootsType rootVals{};        // в разделе 3.1

>};

Концептуально >roots не изменяет объект >Polynomial, с которым работает, но в качестве части кеширующих действий может потребоваться изменение >rootVals и >rootsAreValid. Это классический случай использования >mutable, и именно поэтому эти данные-члены объявлены с данным модификатором.

Представим теперь, что два потока одновременно вызывают >roots для объекта >Polynomial:

>Polynomial p;

>…

>/*----- Поток 1 ----- */     /*------- Поток 2 ------- */

>auto rootsOfP = p.roots();   auto valsGivingZero = p.roots();

Этот клиентский код совершенно разумен. Функция >roots является константной функцией-членом, и это означает, что она представляет операцию чтения. Выполнение операций чтения несколькими потоками одновременно без синхронизации вполне безопасно. Как минимум предполагается, что это так. В данном случае это не так, поскольку в функции >roots один или оба эти потока могут попытаться изменить члены-данные >rootsAreValid и >rootVals. Это означает, что данный код может одновременно читать и записывать одни и те же ячейки памяти без синхронизации, а это — определение гонки данных. Такой код имеет неопределенное поведение.

Проблема заключается в том, что функция >roots объявлена как >const, но не является безопасной с точки зрения потоков. Объявление >const является корректным как в С++11, так и в С++98 (вычисление корней полинома не изменяет сам полином), так что коррекция нужна для повышения безопасности потоков.

Простейший способ решения проблемы обычно один: применение >mutex:

>class Polynomial {

>public:

> using RootsType = std::vector;

> RootsType roots() const {

>  std::lock_guard g(m); // Блокировка мьютекса

>  if (!rootsAreValid) {             // Если кеш некорректен

>   …                                // Вычисление корней

>   rootsAreValid = true;

> }

> return rootVals;

>} // Разблокирование

>private:

> mutable std::mutex m;

> mutable bool rootsAreValid{ false };

> mutable RootsType rootVals{};

>};

Мьютекс >std::mutex m объявлен как >mutable, поскольку его блокировка и разблокирование являются неконстантными функциями, а в противном случае в константной функции-члене >roots мьютекс >m рассматривается как константный объект.

Следует отметить, что поскольку >std::mutex не может быть ни скопирован, ни перемещен, побочным эффектом добавления >m к >Polynomial является то, что >Polynomial теряет возможность копирования и перемещения.

В некоторых ситуациях мьютекс является излишеством. Например, если все, что вы делаете, — это подсчитываете, сколько раз вызывается функция-член, то часто более дешевым средством является счетчик >std::atomic (т.e. счетчик, для которого гарантируется атомарность операций — см. раздел 7.6). (Действительно ли это более дешевое средство, зависит от аппаратного обеспечения и реализации мьютексов в вашей стандартной библиотеке.) Вот как можно использовать >std::atomic для подсчета вызовов:

>class Point { // Двумерная точка

>public:

> …

> double distanceFromOrigin() // См. описание noexcept

> const noexcept              // в разделе 3.8

> {

>  ++callCount;               // Атомарный инкремент

>  return std::hypot(x, y);   // std::hypot - новинка C++11

> }

>private:

> mutable std::atomic callCount{ 0 };

> double x, y;

>};

Как и >std::mutex, >std::atomic невозможно копировать, так что наличие >callCount в >Point означает, что >Point также невозможно копировать.

Поскольку операции над переменными >std::atomic зачастую менее дорогостоящи, чем захват и освобождение мьютекса, вы можете соблазниться использовать >std::atomic больше, чем следует. Например, в классе, кеширующем дорогостоящее для вычисления значение >int, вы можете попытаться использовать вместо мьютекса пару переменных >std::atomic:

>class Widget {

>public:

> …

> int magicValue() const {

>  if (cacheValid) return cachedValue;

>  else {

>   auto val1 = expensiveComputation1();

>   auto val2 = expensiveComputation2();

>   cachedValue = val1 + val2; // Часть 1

>   cachevalid = true;         // Часть 2

>   return cachedValue;

>  }

> }

>private:

> mutable std::atomic cacheValid{ false };

> mutable std::atomic cachedValue;

>};

Этот способ работает, но иногда выполняет существенно большую работу, чем требуется. Рассмотрим такой сценарий.


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