С. Мейерс - Эффективный и современный C++ (1114942), страница 40
Текст из файла (страница 40)
Чтобы этого не произошло, к Т применяется свойство типа(см. раздел 3.3) s t d : : remove_reference, тем самым обеспечивая применение " & &" к типу,не являющемуся ссылкой. Это гарантирует, что s t d : : move действительно возвращаетrvа\uе-ссылку, и это важно, поскольку rvа\uе-ссылки, возвращаемые функциями, являются rvalue. Таким образом, std : : move приводит свой аргумент к rvalue, и это все, чтоона делает.В качестве небольшого отступления скажем, что s td : : move можно реализоватьв С++ 14 меньшими усилиями. Благодаря выводу возвращаемого типа функции (см.
раздел 1 .3) и шаблону псевдонима s t d : : remove_reference_t (см. раздел 3.3) std : : moveможно записать следующим образом:// С++1 4 ; находится вtemplate<typename Т>move ( T & & param) / / пространстве имен stddecltype (auto)[using ReturnType ramove_reference_t<T>& & ;return static_cast<ReturnType> (param) ;=Легче для восприятия, не так ли?Поскольку s t d : : move ничего не делает, кроме приведения своего аргумента к rvalue,были предложения дать этому шаблону другое имя, например rva lue_cas t .
Как бытам ни было, у нас имеется имя std : : move, так что нам важно запомнить, что это имяstd : : move делает и чего не делает. Итак, оно выполняет приведение. И оно ничего непереносит.Конечно, rvalue являются кандидатами на перемещение, поэтому применениеstd : : move для объекта сообщает компилятору, что объект предназначается для перемещения. Вот почему std : : move имеет такое имя: чтобы легко распознавать объекты,которые могут быть перемещены.По правде говоря, rvalue обь1чно являются всего лишь кандидатами для перемещения.Предположим, что вы пишете класс, представляющий аннотации. Конструктор классаполучает параметр s t d : : st ring, представляющий аннотацию, и копирует значение параметра в член-данные. С учетом информации из раздела 8 .
1 вы объявляете параметр какпередаваемый по значению:class Annotatioп {puЫ i c :explicit Annotation ( std: : strinq text ) ; / / Параметр};11 копируемый , так что согласно/ / разделу 8 . 1 он передается по значениюНо конструктору Annotation требуется только прочесть значение text. Ему не нужно егомодифицировать.
В соответствии с освященной веками традицией использования cons tвезде, где только можно, в ы переписываете свое объявление, делая text константой:class AnnotationpuЫic :5.1 .Аэы std::move и std::forward1 67explicit Annotation ( const std: : s t ring text ) ;};Чтобы избежать дорогостоящей операции копирования t ext в член-данные, вы оставляете в силе совет из раздела 8 . 1 и применяете std : : move к text, тем самым получая rvalue:class Annotation (puЫ i c :explicit Annotat ion ( const s t d : : st ring text )value (std: : move (text) )11 " Перемещение" text в value ;11 этот код не делает того ,( ...
}/ / что от него ожидается !private :std : : string va lue;};Этот код компилируется. Этот код компонуется. Этот код выполняется. Этот кодустанавливает значение члена-данных va lue равным содержимому строки text. Единственное, что отличает этот код от идеальной реализации ваших намерений, - то, чтоtext не перемещается в va l ue, а копируется. Конечно, text приводится к rvalue с помощью s t d : : move, но text объявлен как const std : : st r i ng, так что перед приведениемtext являлся lvalue типа const s t d : : st r ing, так что результатом приведения являетсяrvalue типа const std : : s t r i ng, и на протяжении всех этих действий константность сохраняется.Рассмотрим, как компиляторы определяют, какой из конструкторов std : : st ring должен быть вызван.
Есть две возможности:class stringpuЬl i c :1 1 s t d : : st ring в действительности представляет// собой typedef для std : : basic_string<char>string ( const string& rhs ) ; / / Копирующий конструкторstring ( string&& rhs ) ;/ / Перемещающий конструктор};В списке инициализации членов конструктора Annotat i on результатом s td : : move( text ) является rvalue типа const std : : st r i ng. Это rvalue нельзя передать перемещающему конструктору s t d : : s t r i ng, поскольку перемещающий конструктор получаетrvalue-ccылкy на неконстантную s t d : : s t r ing. Однако это rvalue может быть передано копирующему конструктору.
поскольку lvalue-ccылкy на const разрешено связыватьс константным rvalue. Таким образом, инициализация члена использует копирующийконструктор s t d : : s t r i ng, несмотря на то что text был приведен к rvalue! Такое поведение имеет важное значение для поддержания корректности const. Перемещение значения из объекта в общем случае модифицирует этот объект, так что язык программирования должен не разрешать передавать константные объекты в функции (такие, какперемещающие конструкторы), которые могут их модифицировать.168Глава 5. Rvа l uе-ссыл ки, семантика перемещений и прямая передачаИз этого примера следует извлечь два урока.
Во-первых, не объявляйте объекты какконстантные, если хотите иметь возможность выполнять перемещение из н их. Запросперемещения к константным объектам молча трансформируется в копирующие операции. Во-вторых, std : : move не только ничего не перемещает самостоятельно, но даже негарантирует, что приведенный этой функцией объект будет иметь право быть перемещенным. Единственное, что точно известно о результате применения s t d : : move к объекту, - это то, что он является rvalue.История с std : : forward подобна истории с std : : move, но тогда как s t d : : move выполняет безоговорочное приведение своего аргумента в rvalue, std : : forward делает этотолько при определенных условиях.
std : : forward является условным приведением. Чтобы понять, когда приведение выполняется, а когда нет, вспомним, как обычно используется std : : forward. Наиболее распространенным сценарием является тот, когда шаблонфункции получает параметр, представляющий собой универсальную ссылку, и которыйпередается другой функции:void process ( const Widget& lvalArg) ; 11 Обработка lvaluevoid process (Widget&& rvalArg ) ;1 1 Обработка rvaluetemplate<typename Т>void logAndProces s ( T & & param)11 шаблон, передающий1 1 param на обработкуauto now =1 1 Получает текущее времяstd : : chrono : : system_clock : : now ( ) ;makeLogEntry ( "Bызoв ' process ' " , now) ;process ( std : : forward<T> ( param) ) ;Рассмотрим два вызова logAndProcess, один с lvalue, а другой - с rvalue:Widget w ;logAndProcess (w) ;1 1 Вызов с lvaluelogAndProcess ( s td : : move ( w ) ) ; 11 Вызов с rvalueВ функции logAndProcess параметр param передается функции process.
Функцияprocess перегружена для lvalue и rvalt1e. Вызывая logAndProces s с lvalue, мы, естественно, ожидаем, что lvalue будет передано функции process как lvalue, а вызываяlogAndProcess с rvalue, мы ожидаем, что будет вызвана перегрузка process для rvalue.Однако pa ram, как и все параметры функций, является lvalue.
Каждый вызов processвнутри logAndProcess будет, таким образом, вызывать перегрузку process для lvalue. Дляпредотвращения такого поведения нам нужен механизм для приведения param к rvalueтогда и только тогда, когда аргумент, которым инициализируется param аргумент, переданный logAndProcess,был rvalue. Именно этим и занимается std : : forward. Вотпочему std : : forward представляет собой условное приведение: эта функция выполняетприведение к rvalue только тогда, когда ее аргумент инициализирован rvalue.--5.1 .
Азы std::move и std::forward169Вы можете удивиться, откуда std : : forward может знать, был ли ее аргумент инициализирован rvalue? Например, как в приведенном выше коде s t d : : forward может сказать,был ли param инициализирован с помощью lvalue или rvalue? Краткий ответ заключаетсяв том, что эта информация кодируется в параметре Т шаблона logAndProcess. Этот параметр передается s t d : : forward, которая восстанавливает закодированную информацию.Детальное описание того, как работает данный механизм, вы найдете в разделе 5.6.Учитывая, что и s t d : : move, и s t d : : forward сводятся к приведению и единственная разница между ними лишь в том, что s t d : : move всегда выполняет приведение, в товремя как s t d : : forwardтолько иногда, вы можете спросить, не можем ли мы обойтись без s t d : : move и просто использовать везде s t d : : f o rward.