Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Название:Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год: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;
};
Это гораздо нагляднее и четко выражает намерения автора. Кроме того, компилятор может в этом случае выдать более понятное сообщение об ошибке, и к тому же при попытке скопировать объект внутри функции-члена класса ошибка произойдёт уже на этапе компиляции, а не компоновки.
Читать дальшеИнтервал:
Закладка: