С. Мейерс - Эффективный и современный C++ (1114942), страница 28
Текст из файла (страница 28)
Еслиповедение сгенерированных компилятором функций вас устраивает (т.е. почленное копирование нестатических членов-данных - именно то, что вам надо), то пересмотр кода3.1 1 .Генерация специальных функций-ч л енов1 19будет очень простым делом, поскольку в С++ l l сказать это явно позволяет простая конструкция "= de fault ":class WidgetpuЬlic :11-Widget ( ) ;Пользовательский деструктор11Поведение копирующегоконструктора по умолчанию11 правильное11Widget ( coпst Widge t & )=default;11 Поведение копирующего/ / присваивания по умолчанию11 правильноеWidget &operator= ( coпst Widge t & )=default;};Этот подход часто полезен в полиморфных базовых классах, т.е. в классах, определяющих интерфейсы, посредством которых происходит управление производными классами.Обычно полиморфные базовые классы имеют виртуальные деструкторы, поскольку, еслиэто не так, некоторые операции (например, использование delete или typeid с объектомпроизводного класса через указатель или ссылку на базовый класс) дают неопределенныйили вводящий в заблуждение результат.
Если только класс не наследует деструктор, являющийся виртуальным, единственный способ сделать деструктор виртуальным - явнообъявить его таковым. Зачастую реализация по умолчанию является корректной, и использовать конструкции "= default" - хороший способ выразить это. Однако пользовательский деструктор подавляет генерацию перемещающих операций, так что, если требуется поддержка перемещаемости,default " зачастую находит второе применение.Объявление перемещающих операций отключает копирующие операции, так что, есликопируемость также желательна, это делает еще один круг "= default":"=class BasepuЫic :virtual -Base ( )=default;Баsе (Баsе&&) = dafault;Базе& operator= (вaзe&&)/ / Делает деструктор виртуальным/ / Поддержка перемещенияdefault;Бase (const вазе&) = dafault ; / / Поддержка копированияБазе& operator= (const вазе&) = default;};Фактически, даже если у вас есть класс, в котором компиляторы могут генерироватькопирующие и перемещающие операции и в котором генерируемые функции ведут себятак, как надо, вы можете выбрать стратегию их объявления и применения конструкции120Гnава 3.
Переход к современному С++default" в качестве определений. Это требует большего количества работы, но делаетваши намерения более ясными, и это может помочь обойти некоторые довольно трудно выявляемые ошибки. Предположим, например, что у нас есть класс, представляющийтаблицу строк, т.е. структуру данных, которая обеспечивает быстрый поиск строковогозначения по его целочисленному идентификатору:class St ringTaЫepuЫic :StringTaЬle ( ) { }1 1 Функции вставки, удаления, поиска и т . п .
, но нет11 функциональности копирования/перемещения/деструкцииprivate :std : : map< int , std : : string> va lues ;};Предположим, что в классе не объявлены н и копирующие операции, н и перемещающие операции, ни деструктор, так что компиляторы автоматически сгенерируют этифункции при их использовании. Это очень удобно.Но предположим, что немного позже решено записывать в журнал конструированиепо умолчанию и деструкцию таких объектов. Добавление соответствующей функциональности выполняется очень просто:class StringTaЬlepuЫ i c :St ringTaЬle ( ){ makeLogEntry ( "Соэдание StringTaЬle " } ; }1 1 Добавлено�stringТaЫe ( )/ / Добавлено{ mаkеLоgЕntrу ( "Уничтожение StringTaЫe " } ;11 Прочие функции,// как и раньшеprivate :std : : map< int , std : : st ring> va lues; // Как и раньше};Выглядит разумно, но объявление деструктора потенциально имеет важное побочное действие: оно предотвращает генерацию перемещающих операций.
Создание классана эти операции не влияет. Таким образом, код, вероятно, будет без проблем компилироваться, выполняться и проходить функциональное тестирование.Сюда включается и тестирование функциональности перемещения, поскольку,хотя даже этот класс и не является перемещаемым, запрос на перемещение будет компилироваться и работать. Такой запрос, как отмечалось ранее в данном разделе, приводит к выполнению копирования. Это означает, что код, "перемещающий" объектыSt ringTaЬle, в действительности их копирует, т.е. копирует лежащие в их основе объекты s t d : : map< i nt , s t d : : s t r i ng>.
А копирование std : : map < i nt , std : : st ring>,скорее всего, окажется на порядки медленнее перемещения. Простое добавление деструктора к классу может тем самым внести значительные проблемы, связанные3.1 1 . Генерация специальных функций-членов121с производительностью! Если бы копирующие и перемещающие операции были явноопределены как "= defaul t ", такая проблема не могла бы возникнуть.Теперь, благополучно пережив мою бесконечную болтовню о правилах, управляющих операциями копирования и перемещения в С++ 1 1, вы можете озадачиться: когда жея, наконец, обращу свое внимание на две другие специальные функции - конструкторпо умолчанию и деструктор? Да прямо сейчас, в этом предложении, и только в нем, потому что для этих функций-членов почти ничего не изменилось: правила в С++ 1 1 практически те же, что и в С++98.Резюмируем правила С++ 1 1 , управляющие специальными функциями-членами.•Конструктор по умолчанию.•Правила по сути те же, что и в С++98; единственное отличие заключается в том, что деструктор по умолчанию является noexcept (см.
раздел 3.8). Каки в С++98, деструктор является виртуальным, только если виртуальным являетсядеструктор базового класса.•Копирующий конструктор. То же поведение времени выполнения, что и в С++98:почленное копирующее конструирование нестатических данных-членов. Генерируется, только если класс не содержит пользовательского копирующего конструктора. Удаляется, если класс объявляет перемещающую операцию.
Генерация этойфункции в классе с пользовательским оператором копирующего присваиванияили деструктором является устаревшей и может быть отменена в будущем.•То же поведение времени выполнения,что и в С++98: почленное копирующее присваивание нестатических данных-членов. Генерируется, только если класс не содержит пользовательского копирующегооператора присваивания. Удаляется, если класс объявляет перемещающую операцию.
Генерация этой функции в классе с пользовательским копирующим кон структором или деструктором является устаревшей и может быть отменена в будущем.•Перемещающий конструктор и перемещающий оператор присваивания. Каждая из этих функций выполняет почленное перемещение нестатических членовданных. Генерируется, только если класс не содержит пользовательских копирующих операций, пользовательских перемещающих операций или пользовательскийдеструктор.Правила те же, что и в С++98.
Генерируется, толькоесли класс не содержит пользовательских конструкторов.Деструктор.Оператор копирующеrо присваивания.Обратите внимание, что в приведенных правилах ничего не говорится о том, что наличие шаблона функции-члена препятствует компиляторам генерировать специальныефункции-члены. Это означает, что, если Widget имеет следующий вид:class Widget {template<typename Т>Widget ( const Т& rhs ) ;1 22Глава 3. Переход к современному С++11 Создание Widget11 из чего угодноtemplate<typename Т>11 Присваивание WidgetWidget& operator= ( const Т& rhs ) ; / / чего угодно);компиляторы по-прежнему будут генерировать копирующие и перемещающие операции для Widget (в предположении выполнения обычных условий, регулирующих их генерацию), несмотря на то что эти шаблоны могут инстанцироваться как копирующийконструктор и копирующий оператор присваивания (это произойдет в случае, когда тпредставляет собой W i dg e t ) .
По всей вероятности, это выглядит как крайний случай,едва стоящий ознакомления с ним, однако я упоминаю его не случайно. В разделе 5.4 выувидите, что все это может иметь важные последствия.Следует запомнить•Специальные функции-члены - это те функции-члены, которые компиляторы могут генерировать самостоятельно: конструктор по умолчанию, деструктор, копирующие и перемещающие операции.•Перемещающие операции генерируются только для классов, в которых нет явнообъявленных перемещающих операций, копирующих операций и деструктора.•Копирующий конструктор генерируется только для классов, в которых нет явнообъявленного копирующего конструктора, и удаляется, если объявляется перемещающая операция.
Копирующий оператор присваивания генерируется толькодля классов, в которых нет явно объявленного копирующего оператора присваивания, и удаляется, если объявляется перемещающая операция. Генерация копирующих операций в классах с явно объявленным деструктором является устаревшейи может быть отменена в будущем.•Шаблоны функций-членов не подавляют генерацию специальных функций-членов.3.1 1 .Генерация специальных функций-членов1 23ГЛАВА 4И нте лл ект уа ль н ы е у ка з ате л иПоэты и композиторы пишут о любви. Иногда - о подсчетах. Иногда - и о том,и о другом одновременно. Стоит только вспомнить Элизабет Баррет Браунинг (ElizabethBarrett Browning) с ее "Как я люблю тебя? Позволь мне счесть .