Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Название:Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год:2012
- Город:Москва
- ISBN:978-5-94074-448-1
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ краткое содержание
Книга «Параллельное программирование на С++ в действии» не предполагает предварительных знаний в этой области. Вдумчиво читая ее, вы научитесь писать надежные и элегантные многопоточные программы на С++11. Вы узнаете о том, что такое потоковая модель памяти, и о том, какие средства поддержки многопоточности, в том числе запуска и синхронизации потоков, имеются в стандартной библиотеке. Попутно вы познакомитесь с различными нетривиальными проблемами программирования в условиях параллелизма.
Параллельное программирование на С++ в действии. Практика разработки многопоточных программ - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
В классе X
перемещающий конструктор — всего лишь оптимизация, но в ряде случаев такой конструктор имеет смысл определять, даже когда копирующий конструктор не предоставляется. Например, идея std::unique_ptr<>
в том и заключается, что любой ненулевой экземпляр является единственным указателем на свой объект, поэтому копирующий конструктор лишен смысла. Однако же перемещающий конструктор позволяет передавать владение указателем от одного объекта другому, поэтому std::unique_ptr<>
можно использовать в качестве возвращаемого функцией значения — указатель перемещается, а не копируется.
Чтобы явно переместить значение из именованного объекта, который больше заведомо не будет использоваться, мы можем привести его к типу r -значения либо с помощью static_cast
, либо путем вызова функции std::move()
:
X x1;
X x2 = std::move(x1);
X x3 = static_cast(x2);
Это особенно удобно, когда требуется переместить значение параметра в локальную переменную или переменную-член без копирования, потому что хотя параметр, являющийся ссылкой на r -значение, и может связываться с r -значениями, но внутри функции он трактуется как l -значение:
void do_stuff(X&& x_) {
X a(x_); ←
Копируется
X b(std::move(x_)); ←
Перемещается
} │
r -значение связывается
do_stuff(X());←┘
со ссылкой на r -значение
X x; │
Ошибка, l -значение нельзя связывать
do_stuff(x);←┘
со ссылкой на r -значение
Семантика перемещения сплошь и рядом используется в библиотеке Thread Library — и в случаях, когда копирование не имеет смысла, но сами ресурсы можно передавать, и как оптимизация, чтобы избежать дорогостоящего копирования, когда исходный объект все равно будет уничтожен. Один пример мы видели в разделе 2.2, где std::move()
использовалась для передачи экземпляра std::unique_ptr<>
новому потоку, а второй — в разделе 2.3, когда рассматривали передачу владения потоком от одного объекта std::thread
другому.
Ни один из классов std::thread
, std::unique_lock<>
, std::future<>
, std::promise<>
, std::packaged_task<>
не допускает копирования, но в каждом из них имеется перемещающий конструктор, который позволяет передавать ассоциированный ресурс другому экземпляру и возвращать объекты этих классов из функций. Объекты классов std::string
и std::vector<>
можно копировать, как и раньше, но дополнительно они обзавелись перемещающими конструкторами и перемещающими операторами присваивания, чтобы избежать копирования данных из r -значений.
Стандартная библиотека С++ никогда не делает ничего с объектом, который был явно перемещён в другой объект, кроме его уничтожения или присваивания ему значения (путем копирования или, что более вероятно, перемещения). Однако рекомендуется учитывать в инвариантах класса состояние перемещен-из. Например, экземпляр std::thread
, содержимое которого перемещено, эквивалентен объекту std::thread
, сконструированному по умолчанию, а экземпляр std::string
, бывший источником перемещения, все же находится в согласованном состоянии, хотя не дается никаких гарантий относительно того, что это за состояние (в терминах длины строки или содержащихся в ней символов).
А.1.2. Ссылки на r -значения и шаблоны функций
Еще один нюанс имеет отношение к использованию ссылок на r -значения в качестве параметров шаблона функции: если параметр функции — ссылка на r -значение типа параметра шаблона, механизм автоматического выведения типа аргумента шаблона заключает, что тип — это ссылка на l -значение, если функции передано l -значение, или обычный не-ссылочный тип, если передано r -значение. Фраза получилась довольно запутанной, поэтому приведём пример. Рассмотрим такую функцию:
template
void foo(T&& t) {}
Если при вызове передать ей r -значение, как показано ниже, то в качестве T
выводится тип этого значения:
foo(42);
foo(3.14159);
fоо(std::string());
Но если вызвать foo
, передав l-значение , то механизм выведения типа решит, что T
— ссылка на l -значение:
int i = 42;
foo(i);
Поскольку объявлено, что параметр функции имеет тип T&&
, то получается, что это ссылка на ссылку, и такая конструкция трактуется как обычная одинарная ссылка. Таким образом, сигнатура функции foo()
такова:
void foo(int& t);
Это позволяет одному шаблону функции принимать параметры, являющиеся как l -, так и r -значениями. В частности, это используется в конструкторе std::thread
(см. разделы 2.1 и 2.2), чтобы в случае, когда переданный допускающий вызов объект является r -значением, его можно было бы не копировать, а переместить во внутреннюю память.
А.2. Удаленные функции
Иногда операция копирования класса лишена смысла. Типичный пример — std::mutex
. Действительно, что должно было бы получиться в результате копирования мьютекса? Другой пример — std::unique_lock<>
, экземпляр этого класса является единственным владельцем удерживаемой им блокировки. Честное копирование в этом случае означало бы, что у блокировки два владельца, а это противоречит определению. Передача владения, описанная в разделе А.1.2, имеет смысл, но это не копирование. Уверен, вы назовете и другие примеры.
Стандартная идиома предотвращения копирования класса хорошо известна — объявить копирующий конструктор и копирующий оператор присваивания закрытыми и не предоставлять их реализации. Если теперь какой-нибудь внешний по отношению к классу код попытается скопировать объект такого класса, то произойдёт ошибка на этапе компиляции, а если то же самое попытается сделать член класса или его друг, — то ошибка на этапе компоновки (так как реализации отсутствуют):
class no_copies {
public:
no_copies(){}
private:
no_copies(no_copies const&); ←
Реализаций нет
no_copies& operator=(no_copies const&);
};
no_copies a; ←
He компилируется
no_copies b(a);
Комитет, разрабатывавший стандарт C++11, конечно, знал об этой идиоме, но счел ее не совсем честным приёмом. Поэтому было решено предоставить более общий механизм, применимый и к другим случаям: объявить функцию удаленной , включив в ее объявление конструкцию = delete
. Тогда класс no_copies
можно записать в виде:
class no_copies {
public:
no_copies() {}
no_copies(no_copies const&) = delete;
no_copies& operator=(no_copies const&) = delete;
};
Это гораздо нагляднее и четко выражает намерения автора. Кроме того, компилятор может в этом случае выдать более понятное сообщение об ошибке, и к тому же при попытке скопировать объект внутри функции-члена класса ошибка произойдёт уже на этапе компиляции, а не компоновки.
Читать дальшеИнтервал:
Закладка: