С. Мейерс - Эффективный и современный C++ (1114942), страница 39
Текст из файла (страница 39)
если бы значенияв структуре Impl могли совместно использоваться несколькими W idget ) , то нашли бы,что советы из данного раздела больше не применимы. Нам бы не потребовалось объявлять деструктор в Widget, а без пользовательского деструктора компиляторы с удовольствием генерировали бы операции перемещения, которые делали бы именно то, чтоот них требуется. То есть при следующем коде в файле widget . hclass Widget {// В файле "widget . h"puЫic :Widget ( ) ;// Нет объявлений деструктора// и перемещающих операцийprivate :struct Impl;};std: : shared_J>tr<Impl> pimpl; // std: : shared_ptr// вместо std : : unique_ptrи приведенном далее коде клиента, который включает заголовочный файл widget .
hWidget wl ;auto w2 ( std : :move (wl ) ) ; 11 Перемещающее конструирование w211 Перемещающее присваивание wlwl = std: :move (w2 ) ;все компилировалось бы и работало именно так, как мы рассчитывали: wl был бы созданконструктором по умолчанию, его значение было бы перемещено в w2, а затем это значение, в свою очередь, было бы перемещено в wl, после чего и wl, и w2 были бы уничтожены (тем самым приводя к уничтожению объекта Widget : : Impl ) .Различие в поведении указателей s td : : unique _pt r и std : : shared_pt r для pimplвытекает из различий путей, которыми эти интеллектуальные указатели поддерживают1 62Глава 4.
Интеллектуальные указателипользовательские удалители. Для std : : unique_pt r тип удалителя является частью типаинтеллектуального указателя, и это позволяет компилятору генерировать меньшиеструктуры данных времени выполнения и более быстрый код. Следствием этой болеевысокой эффективности является то, что указываемые типы должны быть полными, когда используются специальные функции-члены, генерируемые компиляторами (например,деструкторы или перемещающие операции).
В случае s t d : : shared_pt r тип удалителя неявляется частью типа интеллектуального указателя. Это требует больших структур данных времени выполнения и несколько более медленного кода, но зато указываемые типыне обязаны быть полными при применении специальных функций-членов, генерируемыхкомпиляторами.При применении идиомы Pimpl в действительности нет никакого компромисса междухарактеристиками s t d : : un i que_p t r и std : : shared_pt r, поскольку отношения междуклассами наподобие W idget и Widget : : Impl представляют собой исключительное владение, и это делает единственно верным выбором в качестве инструмента интеллектуальный указатель std : : unique_pt r.
Тем не менее стоит знать, что в других ситуациях - ситуациях, в которых осуществляется совместное владение (а следовательно, правильнымвыбором является std : : shared_pt r},нет необходимости прыгать через горящие обручи определений функций, которую влечет за собой применение std : : unique_pt r.-Сnедует запомнить•Идиома Pimpl уменьшает время построения приложения, снижая зависимостикомпиляции между клиентами и реализациями классов.•Для указателей plmpl типа s t d : : unique_pt r следует объявлять специальные функции-члены в заголовочном файле, но реализовывать их в файле реализации.
Поступайте так, даже если реализации функций по умолчанию являются приемлемыми.•Приведенный выше совет применим к интеллектуальному указателю std : : unique_pt r, но не к std : : shared_pt r.4.5. При использовании идиомы указателя на реализацию определяйте специальные" "1 63ГЛАВА SRvalue - cc ы n к и , с емант и кап ереме щ ен и и и п р ямая п ередачаvНа первый взгляд, семантика перемещения и прямой передачи кажется довольнопростой.•Семантика перемещения•Прямая передачапозволяет компиляторам заменять дорогостоящие операции копирования менее дорогими перемещениями.
Так же, как копирующие конструкторы и копирующие операторы присваивания дают вам контроль над тем, чтоозначает копирование объектов, так и перемещающие конструкторы и перемещающие операторы присваивания предоставляют контроль над семантикой перемещения.
Семантика перемещения позволяет также создавать типы, которые могуттолько перемещаться, такие как std : : unique_pt r, std : : future или s t d : : thread.делает возможным написание шаблонов функций, которые принимают произвольные аргументы и передают их другим функциям так, что целевыефункции получают в точности те же аргументы, что и переданные исходным функциям.Rvаluе-ссылки представляют собой тот клей, который соединяет две эти довольноразные возможности.
Это базовый механизм языка программирования, который делаетвозможными как семантику перемещения, так и прямую передачу.С ростом опыта работы с этими возможностями вы все больше понимаете, что вашепервоначальное впечатление было основано только на пресловутой вершине айсберга.Мир семантики перемещения, прямой передачи и rvalue-ccылoк имеет больше нюансов,чем кажется на первый взгляд. Например, std : : move ничего не перемещает, а прямаяпередача оказывается не совсем прямой.
Перемещающие операции не всеrда дешевле копирования, а когда и дешевле, то не всегда настолько, как вы думаете; кроме того, онине всегда вызываются в контексте, где перемещение является корректным. Конструкцияtype & & не всегда представляет rvalue-ccылкy.Независимо от того, как глубоко вы закопались в эти возможности, может показаться, что можно долго копать еще глубже. К счастью, эта глубина не безгранична. Эта главадоведет вас до коренной породы. Когда вы докопаетесь до нее, эта часть С++ 1 1 будетвыглядеть намного более осмысленной. Например, вы познакомитесь с соглашениями по использованию s t d : : move и std : : forward. Вы почувствуете себя намного болеекомфортно, сталкиваясь с неоднозначной природой t ype & & .
Вы поймете причины удивительно разнообразного поведения перемещающих операций. Все фрагменты мозаикивстанут на свои места. В этот момент вы окажетесь там, откуда начинали, потому чтосемантика перемещений, прямая передача и rvаluе-ссылки вновь покажутся вам достаточно простыми. Но на этот раз они будут оставаться для вас такими навсегда.В этой главе особенно важно всегда иметь в виду, что параметр всегда является lvalue,даже если ero тип - rvalue-ccылкa. Иными словами, в фрагментеvoid f (Wiclget&& w ) ;параметр w представляет собой lvalue, несмотря на то что его тип - rvalue-ccылкaна Widget. (Если это вас удивляет, вернитесь к обзору lvalue и rvalue, который содержится во введении.)S.1 .
Азы s td : : move и s td : : forwardПолезно подойти к s t d : : move и std : : forward с точки зрения того, чего они не делают. std : : move ничего не перемещает. std : : forward ничего не передает. Во время выполнения они не делают вообще ничего.
Они не генерируют выполнимый код - ни одного байта.std : : move и std : : forward являются всего лишь функциями (на самом деле - шаблонами функций), которые выполняют приведения. std : : move выполняет безусловноеприведение своего аргумента к rvalue, в то время как std : : forward выполняет приведение только при соблюдении определенных условий.
Это все. Пояснения приводят к новому множеству вопросов, но, по сути, история на этом завершена.Чтобы сделать историю более конкретной, рассмотрим пример реализации std : : moveв С++ 1 1 . Она не полностью соответствует деталям стандарта, но очень близка к этому.template<typename Т>11 В пространстве имен stdtypename remove_reference<T> : : type & &m.ove ( T & & param){us ing ReturnType = 11 Объявление псевдонима ; см.
раздел 3 . 3typename remove_reference<T> : : type & & ;return static_cast<Return'l'ype> (param) ;Я выделил здесь две части кода. Одна - имя функции, потому что спецификация возвращаемого типа достаточно запутанна, и я бы не хотел, чтобы вы в ней заблудились.Вторая - приведение, которое составляет сущность функции. Как вы можете видеть,std : : move получает ссылку на объект (чтобы быть точным - универсальную ссылку;см. раздел 5.2) и возвращает ссылку на тот же объект.Часть & & возвращаемого типа функции предполагает, что s t d : : move возвращаетrvalue-ccылкy, но, как поясняется в разделе 5.6, если тип т является \vаluе-ссылкой, Т & &1 66Глава S. Rvаluе-ссылки, семантика перемещений и прямая передачастановится \vа\uе-ссылкой.