Скотт Мейерс - Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14
- Название:Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14
- Автор:
- Жанр:
- Издательство:Вильямс
- Год:2016
- Город:Москва
- ISBN:978-5-8459-2000-3
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Скотт Мейерс - Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14 краткое содержание
В книге рассматриваются следующие темы. Освоение С++11 и С++14 — это больше, чем просто ознакомление с вводимыми этими стандартами возможностями (например, объявлениями типов
, семантикой перемещения, лямбда-выражениями или поддержкой многопоточности). Вопрос в том, как использовать их эффективно, чтобы создаваемые программы были корректны, эффективны и переносимы, а также чтобы их легко можно было сопровождать. Именно этим вопросам и посвящена данная книга, описывающая создание по-настоящему хорошего программного обеспечения с использованием C++11 и С++14 — т.е. с использованием современного С++.
■ Преимущества и недостатки инициализации с помощью фигурных скобок, спецификации
, прямой передачи и функций
интеллектуальных указателей
■ Связь между
,
, rvalue-ссылками и универсальными ссылками
■ Методы написания понятных, корректных,
лямбда-выражений
■ Чем
отличается от
, как они используются и как соотносятся с API параллельных вычислений С++
■ Какие из лучших методов “старого” программирования на С++ (т.е. С++98) должны быть пересмотрены при работе с современным С++
Более чем 20 лет книги
серии
являются критерием уровня книг по программированию на С++. Понятное пояснение сложного технического материала принесло ему всемирную известность. Он всегда самый желанный гость на международных конференциях, а его услуги консультанта широко востребованы во всем мире.
Скотт Мейерс Эффективный и современный С++, После изучения основ С++ я перешел к изучению того, как применять С++ в промышленном программировании, с помощью серии книг Скотта Мейерса Эффективный С++. Эффективный и современный С++ — наиболее важная из книг серии, предлагающая ключевые рекомендации, стили и идиомы, позволяющие эффективно использовать современный С++. Вы еще не купили эту книгу? Сделайте это прямо сейчас. Герб Саттер,
глава Комитета ISO по стандартизации С++, специалист в области архитектуры программного обеспечения на С++ в Microsoft
Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14 - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Второй важный аспект этого примера заключается в поведении двух последних инструкций — инкремента и декремента ai
. Каждая из этих операций является операцией чтения-изменения-записи (read-modify-write — RMW), выполняемой атомарно. Это одна из приятнейших характеристик типов std::atomic
: если объект std::atomic
создан, все его функции-члены, включая RMW-операции, будут гарантированно рассматриваться другими потоками как атомарные.
В противоположность этому такой же код, но использующий квалификатор volatile
, в многопоточном контексте не гарантирует почти ничего:
volatilevi(0); // Инициализация vi значением 0
vi = 10; // Присваивание vi значения 10
std::cout << vi; // Чтение значения vi
++vi; // Инкремент vi до 11
--vi; // Декремент vi до 10
Во время выполнения этого кода, если другой поток читает значение vi
, он может прочесть что угодно, например -12, 68, 4090727, — любое значение! Такой код обладает неопределенным поведением, потому что эти инструкции изменяют vi
, и если другие потоки читают vi
в тот же момент времени, то эти одновременные чтения и записи памяти не защищены ни std::atomiс
, ни с помощью мьютексов, а это и есть определение гонки данных.
В качестве конкретного примера того, как отличаются поведения std::аtomic
и volatile
в многопоточной программе, рассмотрим простые счетчики каждого вида, увеличиваемые несколькими потоками. Инициализируем каждый из них значением 0:
std::atomic ас(0); // "счетчик atomic"
volatile int vc(0); // "счетчик volatile"
Затем увеличим каждый счетчик по разу в двух одновременно работающих потоках:
/*----- Поток 1------ */ /*----- Поток 2 ------ */
++ас; ++ас;
++vc; ++vc;
По завершении обоих потоков значение ас
(т.e. значение std::atomic
) должно быть равно 2, поскольку каждый инкремент осуществляется как атомарная, неделимая операция. Значение vc
, с другой стороны, не обязано быть равным 2, поскольку его инкремент может не быть атомарным. Каждый инкремент состоит из чтения значения vc
, увеличения прочитанного значения и записи результата обратно в vc
. Но для объектов volatile
не гарантируется атомарность всех трех операций, так что части двух инкрементов vc
могут чередоваться следующим образом.
1. Поток 1 считывает значение vc
, равное 0.
2. Поток 2 считывает значение vc
, все еще равное 0.
3. Поток 1 увеличивает 0 до 1 и записывает это значение в vc
.
4. Поток 1 увеличивает 0 до 1 и записывает это значение в vc
.
Таким образом, окончательное значение vc
оказывается равным 1, несмотря на два инкремента.
Это не единственный возможный результат. В общем случае окончательное значение vc
непредсказуемо, поскольку переменная vc включена в гонку данных, а стандарт однозначно утверждает, что гонка данных ведет к неопределенному поведению; это означает, что компиляторы могут генерировать код, выполняющий буквально все что угодно. Конечно, компиляторы не используют эту свободу для чего-то вредоносного. Но они могут выполнить оптимизации, вполне корректные при отсутствии гонки данных, и эти оптимизации приведут к неопределенному и непредсказуемому поведению при наличии гонки данных.
RMW-операции — не единственная ситуация, в которой применение std::atomic
ведет к успешным параллельным вычислениям, а volatile
— к неудачным. Предположим, что одна задача вычисляет важное значение, требуемое для второй задачи. Когда первая задача вычисляет значение, она должна сообщить об этом второй задаче. В разделе 7.5 поясняется, что одним из способов, которым один поток может сообщить о доступности требуемого значения другому потоку, является применение std::atomic
. Код в задаче, выполняющей вычисление значения, имеет следующий вид:
std::atomicvalAvailable(false);
auto imptValue = computeImportantValue(); // Вычисление значения
valAvailable = true; // Сообщение об этом
// другому потоку
Как люди, читая этот код, мы знаем, что критично важно, чтобы присваивание imptValue
имело место до присваивания valAvailable
, но все компиляторы видят здесь просто пару присваиваний независимым переменным. В общем случае компиляторы имеют право переупорядочить такие независимые присваивания. Иначе говоря, последовательность присваиваний (где а
, b
, x
и y
соответствуют независимым переменным)
а = b;
x = y;
компиляторы могут переупорядочить следующим образом:
x = y;
а = b;
Даже если такое переупорядочение выполнено не будет, это может сделать аппаратное обеспечение (или сделать его видимым таковым для других ядер. если таковые имеются в наличии), поскольку иногда это может сделать код более быстрым.
Однако применение std::atomic
накладывает ограничения на разрешенные переупорядочения кода, и одно такое ограничение заключается в том, что никакой код, предшествующий в исходном тексте записи переменной std::atomic
, не может иметь место (или выглядеть таковым для других ядер) после нее [27] Это справедливо только для std::аtomic , использующих последовательную согласованность , которая является применяемой по умолчанию (и единственной) моделью согласованности дня объектов std::atomic , использующих показанный в этой книге синтаксис. C++11 поддерживает также модели согласованности с более гибкими правилами переупорядочения кода. Такие слабые (или смягченные ) модели делают возможным создание программного обеспечения, работающего более быстро на некоторых аппаратных архитектурах, но применение таких моделей дает программное обеспечение, которое гораздо труднее правильно понимать и поддерживать. Тонкие ошибки в коде с ослабленной атомарностью не являются редкостью даже для экспертов, так что вы должны придерживаться, насколько это возможно, последовательной согласованности.
. Это означает, что в нашем коде
auto imptValue = computeImportantValue(); // Вычисление значения
valAvailable = true; // Сообщение об этом
// другому потоку
компиляторы должны не только сохранять порядок присваиваний imptValue
и valAvailable
, но и генерировать код, который гарантирует, что так же поведет себя и аппаратное обеспечение. В результате объявление valAvailable
как std::atomic
гарантирует выполнение критичного требования к упорядоченности — что значение imptValue
должно быть видимо всеми потоками как измененное не позже, чем значение valAvailable
.
Объявление valAvailable
как volatile
не накладывает такое ограничение на переупорядочение кода:
volatilebool valAvailable(false);
auto imptValue = computeImportantValue();
valAvailable = true; // Другие потоки могут увидеть это
Интервал:
Закладка: