Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Название:Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год:2012
- Город:Москва
- ISBN:978-5-94074-448-1
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ краткое содержание
Книга «Параллельное программирование на С++ в действии» не предполагает предварительных знаний в этой области. Вдумчиво читая ее, вы научитесь писать надежные и элегантные многопоточные программы на С++11. Вы узнаете о том, что такое потоковая модель памяти, и о том, какие средства поддержки многопоточности, в том числе запуска и синхронизации потоков, имеются в стандартной библиотеке. Попутно вы познакомитесь с различными нетривиальными проблемами программирования в условиях параллелизма.
Параллельное программирование на С++ в действии. Практика разработки многопоточных программ - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
• все нестатические переменные-члены данного класса и его базовых классов должны иметь тривиальный тип;
• в классе должен существовать либо тривиальный конструктор по умолчанию, либо constexpr
-конструктор, отличный от копирующего конструктора.
О constexpr
-конструкторах мы поговорим чуть ниже. А пока обратимся к классам с тривиальным конструктором по умолчанию. Пример такого класса приведён ниже:
class CX {
private:
int а;
int b;
public:
CX() = default; ←
(1)
CX(int a_, int b_) : ←
(2)
a(a_), b(b_) {}
int get_a() const {
return a;
}
int get_b() const {
return b;
}
int foo() const {
return a + b;
}
};
Здесь мы явно объявили конструктор по умолчанию (1) умалчиваемым (см. раздел А.3), чтобы сохранить его тривиальность, несмотря на наличие определённого пользователем конструктора (2). Таким образом, этот тип удовлетворяет всем требованиям к литеральному типу и, значит, его можно использовать в константных выражениях. К примеру, можно написать constexpr
-функцию, которая создает новые экземпляры этого класса:
constexpr CX create_cx() {
return CX();
}
Можно также написать простую constexpr
-функцию, которая копирует свой параметр:
constexpr CX clone(CX val) {
return val;
}
Но это практически и всё, что можно сделать, — constexpr
-функции разрешено вызывать только другие constexpr
-функции. Тем не менее, допускается применять спецификатор constexpr
к функциям-членам и конструкторам CX:
class CX {
private:
int а;
int b;
public:
CX() = default;
constexpr CX(int a_, int b_): a(a_), b(b_) {}
constexpr int get_a() const { ←
(1)
return a;
}
constexpr int get_b() { ←
(2)
return b;
}
constexpr int foo() {
return a + b;
}
};
Отметим, что теперь квалификатор const
в функции get_a()
(1)избыточен, потому что он и так подразумевается ключевым словом constexpr
. Функция get_b()
достаточно «константная» несмотря на то, что квалификатор const
опущен (2). Это дает возможность строить более сложные constexpr
-функции, например:
constexpr CX make_cx(int a) {
return CX(a, 1);
}
constexpr CX half_double(CX old) {
return CX(old.get_a()/2, old.get_b()*2);
}
constexpr int foo_squared(CX val) {
return square(val.foo());
}
int array[foo_squared(
half_double(make_cx(10)))]; ←
49 элементов
Всё это, конечно, интересно, но уж слишком много усилий для того, чтобы всего лишь вычислить границы массива или значение целочисленной константы. Основное же достоинство константных выражений и constexpr
-функций в контексте пользовательских типов заключается в том, что объекты литерального типа, инициализированные константным выражением, инициализируются статически и, следовательно, не страдают от проблем, связанных с зависимостью от порядка инициализации и гонок.
CX si = half_double(CX(42, 19));
Это относится и к конструкторам. Если конструктор объявлен как constexpr
, а его параметры — константные выражения, то такая инициализация считается константной инициализацией и происходит на этапе статической инициализации. Это одно из наиболее важных изменений в стандарте C++11 с точки зрения параллелизма : разрешив статическую инициализацию для определенных пользователем конструкторов, мы предотвращаем состояния гонки во время инициализации, поскольку объекты гарантированно инициализируются до начала выполнения программы.
Особенно существенно это для таких классов, как std::mutex
(см. раздел 3.2.1) и std::atomic<>
(см. раздел 5.2.6), поскольку иногда мы хотим, чтобы некий глобальный объект синхронизировал доступ к другим переменным, но так, чтобы не было гонок при доступе к нему самому. Это было бы невозможно, если бы конструктор мьютекса мог стать жертвой гонки, поэтому конструктор по умолчанию в классе std::mutex
объявлен как constexpr
, чтобы инициализация мьютекса всегда производилась на этапе статической инициализации.
А.4.2. constexpr
-объекты
До сих пор мы говорили о применении constexpr
к функциям. Но этот спецификатор можно применять и к объектам. Чаще всего, так делают для диагностики; компилятор проверяет, что объект инициализирован константным выражением, constexpr
-конструктором или агрегатным инициализатором, составленным из константных выражений. Кроме того, объект автоматически объявляется как const
:
constexpr int i = 45;←
Правильно
constexpr std::string s("hello");←┐
Ошибка, std::string —
int foo(); │
не литеральный тип
constexpr int j = foo();←
Ошибка, foo() не объявлена как constexpr
A.4.3. Требования к constexpr
-функциям
Чтобы функцию можно было объявить как constexpr
, она должна удовлетворять нескольким требованиям. Если эти требования не выполнены, компилятор сочтет наличие спецификатора constexpr
ошибкой. Требования таковы:
• все параметры должны иметь литеральный тип;
• возвращаемое значение должно иметь литеральный тип;
• тело функции может содержать только предложение return
и ничего больше;
• выражение в предложении return
должно быть константным;
• любой конструктор или оператор преобразования, встречающийся в выражении для вычисления возвращаемого значения, должен быть объявлен как constexpr
.
На самом деле, это вполне понятные требования: у компилятора должна быть возможность встроить вызов функции в константное выражение, и при этом оно должно остаться константным. Кроме того, запрещается что-либо изменять; constexpr
-функции являются чистыми , то есть не имеют побочных эффектов.
К constexpr
-функциям, являющимся членами класса, предъявляются дополнительные требования:
• constexpr
функции-члены не могут быть виртуальными;
• класс, членом которого является функция, должен иметь литеральный тип.
Для constexpr
-конструкторов действуют другие правила:
• тело конструктора должно быть пустым;
• все базовые классы должны быть инициализированы;
• все нестатические данные-члены должны быть инициализированы;
• все выражения, встречающиеся в списке инициализации членов, должны быть константными;
• конструкторы, выбранные для инициализации данных-членов и базовых классов, должны быть constexpr
-конструкторами;
• все конструкторы и операторы преобразования, используемые для конструирования данных-членов и базовых классов в соответствующем выражении инициализации, должны быть объявлены как constexpr
.
Интервал:
Закладка: