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

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

только с повышением. Согласно обычным правилам разрешения перегрузки точное соответствие побеждает соответствие с повышением, так что вызывается перегрузка для универсальной ссылки.

В этой перегрузке параметр >name связывается с переданным значением типа >short. Таким образом, >name передается с помощью >std::forward функции-члену >emplace объекта >names(std::multiset), которая, в свою очередь, послушно передает его конструктору >std::string. Но конструктора >std::string, который принимал бы значение >short, не существует, так что вызов конструктора >std::string в вызове >multiset::emplace в вызове >logAndAdd неудачен. Все дело в том, что перегрузка для универсальной ссылки точнее соответствует аргументу типа >short, чем перегрузка для >int.

Функции, принимающие универсальные ссылки, оказываются самыми жадными в С++. Они в состоянии выполнить инстанцирование с точным соответствием практически для любого типа аргумента (несколько видов аргументов, для которых это не так, описаны в разделе 5.8). Именно поэтому сочетание перегрузки и универсальной ссылки почти всегда является плохой идеей: перегрузка для универсальных ссылок годится для гораздо большего количества типов аргументов, чем обычно ожидает разработчик перегрузок.

Простой способ свалиться в эту яму — написать конструктор с прямой передачей. Небольшое изменение функции >logAndAdd демонстрирует эту проблему. Вместо написания свободной функции, которая принимает либо >std::string, либо индекс, который можно использовать для поиска >std::string, представим себе класс >Person с конструкторами, которые выполняют те же действия:

>class Person {

>public:

> template

> explicit Person(T&& n)       // Конструктор с прямой передачей

> : name(std::forward(n)){} // инициализирует члены-данные


> explicit Person(int idx)     // Конструктор с параметром int

> : name(nameFromIdx(idx)) {}

> …

>private:

> std::string name;

>};

Как и в случае с >logAndAdd, передача целочисленного типа, отличного от >int (например, >std::size_t, >short, >long и т.п.), будет вызывать перегрузку конструктора для универсальной ссылки вместо перегрузки для >int, и это будет вести к ошибкам компиляции. Однако проблема гораздо хуже, поскольку в Person имеется больше перегрузок, чем видит глаз. В разделе 3.11 поясняется, что при соответствующих условиях С++ будет генерировать как копирующие, так и перемещающие конструкторы, и это так и будет, даже если класс содержит шаблонный конструктор, который при инстанцировании в состоянии дать сигнатуру копирующего или перемещающего конструктора. Если таким образом генерируются копирующий и перемещающий конструкторы для >Person, класс >Person будет выглядеть, по сути, следующим образом:

>class Person {

>public:

> template       // Конструктор с прямой передачей

> explicit Person(T&& n)

> : name(std::forward(n)) {}


> explicit Person(int idx);  // Конструктор от int


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

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


> Person(Person&& rhs);      // Перемещающий конструктор

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

>};

Это приводит к поведению, интуитивно понятному, только если вы потратили на работу с компиляторами и общение с их разработчиками столько времени, что забыли, каково это — быть человеком:

>Person p("Nancy");

>auto cloneOfP(p); // Создание нового объекта Person из p;

>                  // этот код не компилируется!

Здесь мы пытаемся создать объект >Person из другого объекта >Person, что представляется очевидным случаем копирующего конструирования (>p является >lvalue, так что можно выбросить из головы все фантазии на тему “копирования” с помощью операции перемещения). Но этот код не вызывает копирующий конструктор — он вызывает конструктор с прямой передачей. Затем эта функция будет пытаться инициализировать член-данные >std::string объекта >Person значением из объекта >Person (>p). Класс >std::string  не имеет конструктора, получающего параметр типа >Person, так что ваш компилятор будет вынужден просто развести руками и наказать вас длинными и непонятными сообщениями об ошибках.

“Но почему, — можете удивиться вы, — вызывается конструктор с прямой передачей, а не копирующий конструктор? Мы же инициализируем >Person другим объектом >Person!” Да, это так, но компиляторы приносят присягу свято соблюдать правила С++, а правила, имеющие отношение к данной ситуации, — это правила разрешения вызовов перегруженных функций.

Компиляторы рассуждают следующим образом. >cloneOfP инициализируется неконстантным lvalue (>p), а это означает, что шаблонный конструктор может быть инстанцирован для получения неконстантного lvalue типа >Person. После такого инстанцирования класс >Person выглядит следующим образом:

>class Person {

>public:

> explicit Person(Person& n)          // Инстанцирован из

> : name(std::forward(n)) {} // шаблона с прямой

>                                     // передачей


> explicit Person(int idx);  // Как и ранее

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

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


Еще от автора Скотт Мейерс
Эффективное использование 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 так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.