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

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

• Поток вызывает >Widget::magicValue, видит, что >cacheValid равно >false, выполняет два дорогостоящих вычисления и присваивает их сумму переменной >cachedValue.

• В этот момент второй поток вызывает >Widget::magicValue, также видит, что значение >cacheValid равно >false, а потому выполняет те же дорогостоящие вычисления, что и только что завершивший их первый поток. (Этот “второй поток” на самом деле может быть несколькими другими потоками.)

Чтобы справиться с этой проблемой, можно пересмотреть порядок присваиваний значений переменным >cachedValue и >cacheValid, но вы вскоре поймете, что (1) вычислять >val1 и >val2 перед тем, как >cacheValid устанавливается равным >true, по-прежнему могут несколько потоков, тем самым провалив цель нашего упражнения, и (2) на самом деле все может быть еще хуже:

>class Widget {

>public:

>…

> int magicValue() const {

>  if (cacheValid) return cachedValue;

>  else {

>   auto val1 = expensiveComputation1();

>   auto val2 = expensiveComputation2();

>   cacheValid = true;                // Часть 1

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

>  }

> }

>};

Представим, что значение >cacheValid равно >false. Тогда возможно следующее.

• Один поток вызывает >Widget::magicValue и выполняет код до точки, где переменная >cacheValid устанавливается равной >true.

• В этот момент второй поток вызывает >Widget::magicValue и проверяет значение >cacheValid. Увидев, что оно равно >true, поток возвращает >cachedValue, несмотря на то, что первый поток еще не выполнил присваивание этой переменной. Таком образом, возвращенное значение оказывается некорректным.

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

>class Widget {

>public:

> …

> int magicValue() const {

>  std::lock_guardguard(m);// Блокировка m

>  if (cacheValid) return cachedValue;

>  else {

>   auto val1 = expensiveComputation1();

>   auto val2 = expensiveComputation2();

>   cachedValue = val1 + val2;

>   cacheValid = true;

>   return cachedValue;

>  }

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

> …

>private:

> mutable std::mutex m;

> mutable int cachedValue;          // Не атомарное

> mutable bool cacheValid{ false }; // Не атомарное

>};

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

Следует запомнить

• Делайте константные функции-члены безопасными с точки зрения потоков, если только вы не можете быть уверены, что они гарантированно не будут использоваться в контексте параллельных вычислений.

• Использование переменных >std::atomic может обеспечить более высокую по сравнению с мьютексами производительность, но они годятся только для работы с единственной переменной или ячейкой памяти.

3.11. Генерация специальных функций-членов

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

Ну, конечно же, вы отлично все это знаете. Да, да, древняя история: Месопотамия, династия Цинь, Владимир Красное Солнышко, FORTRAN, С++98… Но времена изменились, а с ними изменились и правила генерации специальных функций-членов в С++. Важно быть в курсе новых правил, потому что мало вещей имеют такое же важное значение для эффективного программирования на С++, как знание о том, когда компиляторы молча добавляют функции-члены в ваши классы.


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