С. Мейерс - Эффективный и современный C++ (1114942), страница 27
Текст из файла (страница 27)
Генерация специапь н ых функций-чпеновВ официальной терминологии С++ специальные функции-членыэто те функциичлены, которые С++ готов генерировать сам. С ++ 98 включает четыре такие функции:конструктор по умолчанию, деструктор, копирующий конструктор и оператор копирующего присваивания. Эти функции создаются, только если они необходимы, т.е. если некоторый код использует их без их явного объявления в классе.
Конструктор по умолчанию генерируется только в том случае, если в классе не объявлен ни один конструктор.(Это предотвращает компиляторы от создания конструктора по умолчанию для класса,для которого вы указали, что конструктору требуются аргументы. Сгенерированные-1 16Глава 3 . Переход к современному С++специальные функции-члены неявно являются открытыми и встраиваемыми; они также являются не виртуальными, если только таковой функцией не является деструкторпроизводного класса, унаследованного от базового класса с виртуальным деструктором.В этом случае генерируемый компилятором деструктор производного класса также является виртуальным.Ну, конечно же, вы отлично все это знаете.
Да, да, древняя история: Месопотамия,династия Цинь, Владимир Красное Солнышко, FORTRAN, С++98 . . . Но времена изменились, а с ними изменились и правила генерации специальных функций-членов в С++.Важно быть в курсе новых правил, потому что мало вещей имеют такое же важное значение для эффективного программирования на С++, как знание о том, когда компиляторымолча добавляют функции-члены в ваши классы.Что касается С++ 1 1 , то в клуб специальных функций-членов приняты два новыхигрока: перемещающий конструктор и оператор перемещающего присваивания. Их сигнатуры имеют следующий вид:class WidgetpuЫ i c :Widg8t (Widg8t&& rhs) ;/ / Перемещающий: конструкторWidget& operator= (Widqet&& rhs) ; 11 Оператор перемещающего/ / присваивания);Правила, регулирующие их создание и поведение, аналогичны правилам для их копирующих двойников.
Перемещающие операции генерируются только если они необходимы, и если они генерируются, то выполняют "почленное перемещение" нестатическихчленов-данных класса. Это означает, что перемещающий конструктор создает каждыйнестатический член-данные класса из соответствующего члена его параметра rhs с помощью перемещения, а оператор перемещающего присваивания выполняет перемещающее присваивание каждого нестатического члена-данных из переданного ему параметра.Перемещающий конструктор также выполняет перемещающее конструирование частейбазового класса (если таковые имеются), а оператор перемещающего присваивания выполняет соответственно перемещающее присваивание частей базового класса.Когда я говорю о перемещающей операции над членом-данными или базовым классом, нет никакой гарантии, что перемещение в действительности имеет место.
"Почленные перемещения" в действительности представляют собой запросы на почленное перемещение, поскольку типы, которые не могут быть перемещены (т.е. не обладают поддержкой операций перемещения; например, таковыми являются большинство старыхклассов С++98), будут "перемещены" с помощью операций копирования. Сердцем каждого почленного "перемещения" является применение std : : move к объекту, из котороговыполняется перемещение, а результат используется в процессе разрешения перегрузкифункций для выяснения, должно ли выполняться перемещение или копирование.
Этотпроцесс детально описывается в разделе 5. 1 . В этом разделе просто помните, что почленное перемещение состоит из операций перемещения для тех членов-данных и базовых3.1 1 .Генерация специальных функций-чл енов117классов, которые поддерживают перемещающие операции, и из операций копированиядля тех, которые перемещающие операции не поддерживают.Как и в случае с копирующими операциями, перемещающие операции не генерируются, если вы сами их не объявляете. Однако точные условия, при которых они генерируются, несколько отличаются от условий для копирующих операций.Две копирующие операции независимы одна от другой: объявление одной не препятствует компилятору генерировать другую.
Так что если вы объявляете копирующийконструктор, но не копирующий оператор присваивания, а затем пишете код, которомутребуется копирующее присваивание, то компиляторы будут генерировать оператор копирующего присваивания вместо вас. Аналогично, если вы объявили оператор копирующего присваивания, но не копирующий конструктор, а вашему коду нужен копирующийконструктор, то последний будет сгенерирован компилятором вместо вас.
Это правилоработало в С++98 и остается справедливым в C++ l l .Две перемещающие операции н е являются независимыми. Если вы объявите однуиз них, это не позволит компиляторам сгенерировать вторую. Это объясняется тем, чтоесли вы объявляете, скажем, перемещающий конструктор для вашего класса, то вы указываете, что есть что-то, что при перемещающем конструировании должно быть реализовано иначе, чем почленное перемещение по умолчанию, генерируемое компиляторами.
Но если это что-то неверно при почленном перемещающем конструировании, то,вероятно, оно будет неверно и при почленном перемещающем присваивании. Поэтомуобъявление перемещающего конструктора предохраняет от генерации перемещающегооператора присваивания, а объявление перемещающего оператора присваивания предохраняет от генерации перемещающего конструктора.Кроме того, перемещающие операции не будут генерироваться для любого класса, у которого явно объявлены копирующие операции. Объяснение этому заключается в том, чтообъявление копирующих операций (конструктора и присваивания) указывает, что обычный подход к копированию объекта (почленное копирование) не годится для этого класса,и компиляторы делают заключение, что если для класса не подходит почленное копирование, то, вероятно, почленное перемещение для него тоже не подойдет.Этот вывод справедлив и в обратном направлении.
Объявление в классе перемещающей операции (конструктора или присваивания) приводит к тому, что компиляторыне генерируют копирующие операции. (Копирующие операции отключаются с помощьюих удаления; см. раздел 3.5). В конце концов, если почленное перемещение не является корректным способом перемещения, то нет причин ожидать, что почленное копирование окажется корректным способом копирования.
Это выглядит как возможноенарушение работоспособности кода С++98, поскольку условия, при которых разрешена генерация операций копирования, являются более ограничивающими в C++ l l , чемв С++98, но на самом деле это не так. Код С++98 не может иметь перемещающие операции, поскольку в С++98 нет такого понятия, как "перемещение" объектов. Единственный способ для старого класса иметь пользовательские перемещающие операции - этоесли они будут добавлены в коде С++ 1 1 . Но классы, которые изменены таким образом,1 18Гnава 3 . Переход к совреме нн ому С++чтобы использовать преимущества семантики перемещений, обязаны играть по правилам С++ 1 1 , касающимся генерации специальных функций-членов.Вероятно, вы слышали о рекомендации о большой тройке.
Она утверждает, что есливы объявили хотя бы одну из трех операций - копирующий конструктор, копирующийоператор присваивания или деструктор, - то вы должны объявить все три операции.Это правило вытекает из наблюдения, что необходимость изменения смысла копирующей операции почти всегда подразумевает, что ( l ) какое бы управление ресурсами нивыполнялось в одной копирующей операции, вероятно, такое же управление ресурсамипотребуется и во второй копирующей операции; и (2) деструктор класса также долженучаствовать в управлении ресурсами (обычно - освобождать их). Классическим управляемым ресурсом является память, и именно поэтому все классы стандартной библиотеки, управляющие памятью (например, контейнеры STL, управляющие динамическойпамятью), объявляют всю "большую тройку": обе копирующие операции и деструктор.Следствием правила большой тройки является то, что наличие пользовательскогодеструктора указывает на вероятную неприменимость простого почленного копирования для копирующих операций класса.
Это, в свою очередь, предполагает, что если классобъявляет деструктор, то копирующие операции, по всей вероятности, не должны генерироваться автоматически, так как они будут выполнять неверные действия. Во временапринятия С++98 важность этих рассуждений не была оценена должным образом, такчто в С++98 наличие пользовательского деструктора не влияло на генерацию компиляторами копирующих операций. Это правило перешло и в С++ 1 1 , но только потому, чтоограничение условий, при которых могут автоматически генерироваться копирующиеоперации, сделает некомпилируемым слишком большое количество старого кода.Однако рассуждения, лежащие в основе "правила большой тройки': остаются в силе,и это, в сочетании с наблюдением, что объявление копирующей операции исключает неявную генерацию перемещающих операций, обосновывает тот факт, что С++ 1 1 не генерирует операции перемещения для класса с пользовательским деструктором.Таким образом, перемещающие операции генерируются (при необходимости)для классов, только если выполняются три следующие условия:•в классе не объявлены никакие копирующие операции;•в классе не объявлены никакие перемещающие операции;•в классе не объявлен деструктор.В некоторый момент аналогичные правила могут быть распространены на копирующие операции, поскольку C++ l l выступает против автоматической генерации копирующих операций для классов, объявляющих копирующую операцию или деструктор.Это означает, что если у вас есть код, зависящий от генерации копирующих операцийв классе с объявленным деструктором или одной из копирующих операций, то вы должны рассмотреть обновление таких классов для устранения указанной зависимости.