С. Мейерс - Эффективный и современный C++ (1114942), страница 51
Текст из файла (страница 51)
Она не просто позволяет компиляторам заменять дорогостоящие операции копирования относительно дешевыми перемещениями, но и требует от них этого(при выполнении надлежащих условий). Возьмите ваш код С++98, перекомпилируйте егос помощью компилятора и стандартной библиотеки С++ 1 1 и - о чудо! - ваша программа заработает быстрее.Семантика перемещения действительно в состоянии осуществить все это - и потомудостойна легенды. Однако легенды, как правило, - это результат преувеличения. Цельданного раздела - спустить вас с небес на землю.Начнем с наблюдения, что многие типы не поддерживают семантику перемещения.Вся стандартная библиотека С++98 была переработана с целью добавления операцийперемещения для типов, в которых перемещение могло быть реализовано быстрее копирования, и реализации компонентов библиотеки были пересмотрены с целью использования преимуществ новых операций; однако есть вероятность, что вы работаете с кодом,который не был полностью переделан под C++ l l .
Для типов в ваших приложениях (илив используемых вами библиотеках), в которые не были внесены изменения для С++ 1 1 ,мало пользы от наличия поддержки перемещения компилятором. Да, С++ 1 1 готов генерировать перемещающие операции для классов, в которых они отсутствуют, но этопроисходит только для классов, в которых не объявлены копирующие операции, перемещающие операции или деструкторы (см. раздел 3. 1 1 ). Члены-данные базовых классовтипов, в которых перемещения отключены (например, путем удаления перемещающихопераций; см.
раздел 3.5) также подавляют перемещающие операции, генерируемые208Глава 5. Rvаluе-ссылки, семантика перемещений и прямая передачакомпиляторами. Для типов без явной поддержки перемещения и типов, которые не могутпретендовать на перемещающие операции, генерируемые компилятором, нет основанийожидать что С++ 1 1 обеспечит повышение производительности по сравнению с С++98.Даже типы с явной поддержкой перемещений не могут обеспечить все, на что вы надеетесь.
Например, все контейнеры стандартной библиотеки С++ 1 1 поддерживают перемещение, но было бы ошибкой считать, что перемещение является дешевой операциейдля всех контейнеров. Для одних контейнеров это связано с тем, что нет никакого действительно дешевого способа перемещения их содержимого. Для других - с тем, чтодействительно дешевые перемещающие операции предлагаются контейнерами с оговорками, которым не удовлетворяют конкретные элементы контейнера.Рассмотрим новый контейнер C++ l l - s t d : : a r r a y . Контейнер s t d : : a r r a y,по сути, представляет собой встроенный массив с SТL-интерфейсом.
Он фундаментально отличается от других стандартных контейнеров, которые хранят свое содержимое в динамической памяти. Объекты таких типов контейнеров концептуально содержат (в качестве членов-данных) только указатель на динамическую память, хранящуюсодержимое контейнера. (Действительность более сложна, но для наших целей эти отличия не играют роли.) Наличие такого указателя позволяет перемещать содержимоевсего контейнера на константное время: просто копируя указатель на содержимое контейнера из исходного контейнера в целевой и делая указатель исходного контейнеранулевым:std: : vector<Widget> vw l ;// Размещение данных в vwlvwlWidgetsvwl/ / Перемещение vwl в vw2 .
ВЬПlолняется11 за константное время, изменяя// только указатели в vwl и vw2auto vw2std : : move (vwl ) ;WidgetslnиllJ=Объекты std : : array не содержат такого указателя, поскольку данные, содержащиесяв std : : array, хранятся непосредственно в объекте std : : a rray:std: : array<Widge t , 1 0000> awl ;11 Размещение данных в vwl1awl'Widgets��Ф 4J4ФZ!&!QZLCJtЮИ Ц щ$1.Цawl// Перемещение vwl в vw2 . ВЬПlолняется// за линейное время . Все элементы11 awl перемещаются в aw2auto aw2std: : move (awl ) ;=Widgets (перемещение отсюда)·aw2Widgets ((перемещение сюда)Обратите внимание, что все элементы из awl перемещаются в aw2.
В предположении, что W idget представляет собой тип, операция перемещения которого выполняетсябыстрее операции копирования, перемещение std : : array элементов Widget будет более5.7.Считайте, что перемеща ющие операции отсутствуют, дороrи ипи не используются209быстрым, чем копирование того же std : : array. Поэтому std : : a rray предлагает поддержку перемещения. И копирование, и перемещение std : : a rray имеют линейное времяработы, поскольку должен быть скопирован или перемещен каждый элемент контейнера.Это весьма далеко от утверждения "перемещение контейнеров теперь такое же дешевое,как и копирование указателей", которое иногда приходится слышать.С другой стороны, std : : s t r i ng предлагает перемещение за константное время и копирование - за линейное. Создается впечатление, что в этом случае перемещение быстрее копирования, но это может и не быть так. Многие реализации строк используютоптимизацию малых строк (small string optimization - SSO).
При использовании SSO"малые" строки (например, размером не более 1 5 символов) хранятся в буфере в самомобъекте s t d : : s t r i ng; выделение динамической памяти не используется. Перемещениемалых строк при использовании реализации на основе SSO не быстрее копирования, поскольку трюк с копированием только указателя на данные, который в общем случае обеспечивает повышение эффективности, в данном случае не применим.Мотивацией применения SSO является статистика, указывающая, что короткие строки являются нормой для многих приложений. С помощью внутреннего буфера для хранения содержимого таких строк устраняется необходимость динамического выделенияпамяти для них, и это, как правило, дает выигрыш в эффективности. Следствием этоговыигрыша является то, что перемещение оказывается не быстрее копирования" .
Хотядля любителей наполовину полного стакана·' можно сказать, что для таких строк копирование не медленнее, чем перемещение.Даже для типов, поддерживающих быстрые операции перемещения, некоторые кажущиеся очевидными ситуации могут завершиться созданием копий. В разделе 3.8 поясняется, что некоторые контейнерные операции в стандартной библиотеке предполагают строгие гарантии безопасности исключений и что для гарантии того, что старыйкод С++98, зависящий от этой гарантии, не станет неработоспособным при переходена С++ 1 1 , операции копирования могут быть заменены операциями перемещения, только если известно, что последние не генерируют исключений. В результате, даже если типпредоставляет перемещающие операции, более эффективные по сравнению с соответствующими копирующими операциями, и даже если в определенной точке кода перемещающая операция целесообразна (например, исходный объект представляет собойrvalue), компиляторы могут быть вынуждены по-прежнему вызывать копирующие операции, поскольку соответствующая перемещающая операция не объявлена как noexcept.Таким образом, имеется ряд сценариев, в которых семантика перемещения С++ 1 1 непригодна.•Отсутствие перемещающих операций.
Объект, из которого выполняется перемещение, не предоставляет перемещающих операций. Запрос на перемещение, такимобразом, превращается в запрос на копирование.•Перемещение не быстрее. Объект, из которого выполняется перемещение, имеетперемещающие операции, которые не быстрее копирующих.' Известная история о том, что об одном и том же до сре11ины напол11е1111ом стакане пессимистыговорят, что он наполовину пуст, а оптимисты - что он наполовину полон. - Примеч. пер.210Глава S. Rvаluе-ссы л ки, семантика перемещений и прямая передача•Контекст, в котором должно иметь место перемещение, требует операцию, не генерирующую исключения, но операция перемещенияне объявлена как noexcept.Перемещение неприменимо.Стоит упомянуть также еще один сценарий, когда семантика перемещения не приводит к повышению эффективности.•Исходный объект является lvalue.
За очень малыми исключениями (см., например,раздел 5.3) только rvalue могут использоваться в качестве источника перемещающейоперации.Но название этого раздела предполагает отсутствие перемещающих операций, ихдороговизну или невозможность использования. Это типично для обобщенного кода,например, при написании шаблонов, поскольку вы не знаете всех типов, с которымипридется работать.
В таких условиях вы должны быть настолько консервативными в отношении копирования объектов, как будто вы работаете с С++98, - до появления семантики перемещения. Это также случай "нестабильного" кода, т.е. кода, в котором характеристики используемых типов относительно часто изменяются.Однако зачастую вам известно, какие типы использует ваш код, и вы можете положиться на неизменность их характеристик (например, на поддержку ими недорогихперемещающих операций). В этом случае вам не надо делать такие грустные предположения. Вы просто изучаете детали поддержки операций перемещения, используемых вашими типами. Если эти типы предоставляют недорогие операции перемещения и есливы используете объекты в контекстах, в которых эти операции будут вызываться, можетебезопасно положиться на семантику перемещений при замене копирующих операций ихменее дорогими перемещающими аналогами.Сnедует запомнить•Считайте, что перемещающие операции отсутствуют, дороги или не используются.•В коде с известными типами или поддержкой семантики перемещения нет необходимости в таких предположениях.5 .8.
Познакомьтесь с сnучаями некорректно йработы прямой передачиОдной из ярких звезд на небосклоне С++ 1 1 является прямая передача (perfectforwarding). Можно сказать, идеально прямая. Но, как говорит наука, даже пространствоискривляется, так что есть идеальная прямота, а есть реальная. Прямая передача С++ 1 1очень хороша, но достигает истинного совершенства, только если вы готовы игнорировать небольшие искривления. В данном разделе вы познакомитесь с этими маленькимиискривлениями.Перед тем как перейти к их изучению, стоит посмотреть, что мы подразумеваемпод "прямой передачей". Под передачей подразумевается, что одна функция передает5 .8.П ознакомьтесь с сnучаями некорректной работы прямой передачи21 1своим параметры другой функции.