Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14 - [80]

Шрифт
Интервал

>};

В инструкции

>auto cloneOfP(p);

>p может быть передан либо копирующему конструктору, либо инстанцированному шаблону. Вызов копирующего конструктора для точного соответствия типа параметра требует добавления к >p модификатора >const; вызов инстанцированного шаблона никаких добавлений не требует. Таким образом, перегрузка, сгенерированная из шаблона, представляет собой лучшее соответствие, так что компиляторы делают то, для чего предназначены: генерируют вызов той функции, которая соответствует наилучшим образом. “Копирование” неконстантных lvalue типа >Person, таким образом, осуществляется конструктором с прямой передачей, а не копирующим конструктором.

Если мы немного изменим пример, так, чтобы копируемый объект был константным, то увидим совершенно иную картину:

>const Person cp("Nancy"); // Теперь объект константный

>auto cloneOfP(cp);        // Вызов копирующего конструктора!

Поскольку копируемый объект теперь объявлен как >const, он в точности соответствует типу параметра, получаемого копирующим конструктором. Шаблонизированный конструктор также может быть инстанцирован таким образом, чтобы иметь ту же сигнатуру:

>class Person public:

> explicit Person(const Person& n); // Инстанцирован из шаблона

> Person (const Person& rhs);       // Копирующий конструктор

>                                   // (сгенерирован компилятором)

>};

Но это не имеет значения, поскольку одно из правил разрешения перегрузок в С++ гласит, что в ситуации, когда инстанцирование шаблона и нешаблонная функция (т.e. "нормальная" функция) имеют одинаково хорошее соответствие, предпочтение отдается нормальной функции. Поэтому все козыри оказываются на руках копирующего конструктора (нормальной функции) с той же самой сигнатурой.

(Если вам интересно, почему компиляторы создают копирующий конструктор, если они могут инстанцировать шаблонный конструктор с той же сигнатурой, обратитесь к разделу 3.11.)

Взаимодействие между конструкторами с прямой передачей и сгенерированными компилятором операциями копирования и перемещения становится еще более сложным, когда в картину включается наследование. В частности, обычные реализации копирующих и перемещающих операций производного класса ведут себя совершенно неожиданно. Взгляните на следующий код:

>class SpecialPerson: public Person {

>public:

> SpecialPerson(const SpecialPerson& rhs) // Копирующий

> : Person(rhs)            // конструктор; вызывает конструктор

> { … }                    // базового класса с прямой передачей!


> SpecialPerson(SpecialPerson&& rhs)      // Перемещающий

> : Person(std::move(rhs)) // конструктор; вызывает конструктор

> { … }                    // базового класса с прямой передачей!

>};

Как указывают комментарии, копирующий и перемещающий конструкторы производного класса не вызывают копирующий и перемещающий конструкторы базового класса; они вызывают конструктор базового класса с прямой передачей! Чтобы понять, почему, обратите внимание, что функции производного класса используют аргументы типа >SpecialPerson для передачи в базовый класс, после чего в игру вступает разрешение перегрузок для конструкторов в классе >Person. В конечном итоге код не будет компилироваться, потому что у >std::string нет никакого конструктора, принимающего >SpecialPerson.

Я надеюсь, что теперь я убедил вас, что перегрузка для параметров, являющихся универсальными ссылками, — это то, чего лучше избегать, насколько это возможно. Но если перегрузка для универсальной ссылки — плохая идея, то что же делать, если вам нужна функция, которая выполняет передачу большинства типов аргументов, но при этом должна обрабатывать некоторые из них особым образом? Это яйцо может быть разбито массой способов. Этих способов так много, что я посвятил им целый раздел — раздел 5.5, который идет сразу после того, который вы сейчас читаете. Не останавливайтесь, и вы попадете прямо в него.

Следует запомнить

• Перегрузка для универсальных ссылок почти всегда приводит к тому, что данная перегрузка вызывается чаще, чем вы ожидаете.

• Особенно проблематичны конструкторы с прямой передачей, поскольку они обычно соответствуют неконстантным lvalue лучше, чем копирующие конструкторы, и могут перехватывать вызовы из производного класса копирующих и перемещающих конструкторов базового класса.

5.5. Знакомство с альтернативами перегрузки для универсальных ссылок

В разделе 5.4 поясняется, что перегрузка для универсальных ссылок может привести к целому ряду проблем как для автономных функций, так и для функций-членов (в особенности для конструкторов). Тем не менее в нем также приводятся примеры, когда такая перегрузка может оказаться полезной, если только она будет вести себя так, как мы хотим! В этом разделе исследуются способы достижения желаемого поведения либо путем проектирования, позволяющего избежать перегрузок для универсальных ссылок, либо путем применения их таким образом, чтобы ограничить типы аргументов, которым они могут соответствовать.

Ниже использованы примеры, представленные в разделе 5.4. Если вы читали его давно или вовсе не читали, просмотрите указанный раздел, прежде чем читать данный.


Еще от автора Скотт Мейерс
Эффективное использование STL

В этой книге известный автор Скотт Мейерс раскрывает секреты настоящих мастеров, позволяющие добиться максимальной эффективности при работе с библиотекой STL.Во многих книгах описываются возможности STL, но только в этой рассказано о том, как работать с этой библиотекой. Каждый из 50 советов книги подкреплен анализом и убедительными примерами, поэтому читатель не только узнает, как решать ту или иную задачу, но и когда следует выбирать то или иное решение — и почему именно такое.


Как функции, не являющиеся методами, улучшают инкапсуляцию

Когда приходится инкапсулировать, то иногда лучше меньше, чем большеЯ начну со следующего утверждения: Если вы пишете функцию, которая может быть выполнена или как метод класса, или быть внешней по отношению к классу, Вы должны предпочесть ее реализацию без использования метода. Такое решение увеличивает инкапсуляцию класса. Когда Вы думаете об использовании инкапсуляции, Вы должны думать том, чтобы не использовать методы.Удивлены? Читайте дальше.


Рекомендуем почитать
Pro Git

Разработчику часто требуется много сторонних инструментов, чтобы создавать и поддерживать проект. Система Git — один из таких инструментов и используется для контроля промежуточных версий вашего приложения, позволяя вам исправлять ошибки, откатывать к старой версии, разрабатывать проект в команде и сливать его потом. В книге вы узнаете об основах работы с Git: установка, ключевые команды, gitHub и многое другое.В книге рассматриваются следующие темы:основы Git;ветвление в Git;Git на сервере;распределённый Git;GitHub;инструменты Git;настройка Git;Git и другие системы контроля версий.


Java 7

Рассмотрено все необходимое для разработки, компиляции, отладки и запуска приложений Java. Изложены практические приемы использования как традиционных, так и новейших конструкций объектно-ориентированного языка Java, графической библиотеки классов Swing, расширенной библиотеки Java 2D, работа со звуком, печать, способы русификации программ. Приведено полное описание нововведений Java SE 7: двоичная запись чисел, строковые варианты разветвлений, "ромбовидный оператор", NIO2, новые средства многопоточности и др.


MFC и OpenGL

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Симуляция частичной специализации

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Обработка событий в С++

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Питон — модули, пакеты, классы, экземпляры

Python - объектно-ориентированный язык сверхвысокого уровня. Python, в отличии от Java, не требует исключительно объектной ориентированности, но классы в Python так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.