Вход/Регистрация
Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ
вернуться

Майерс Скотт

Шрифт:

shared_ptr& operator=(shared_ptr<Y> const& r); // присваивания

...

};

Что следует помнить

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

• Если вы объявляете шаблоны обобщенных конструкторов копирования или обобщенного оператора присваивания, то по-прежнему должны объявить обычный конструктор копирования и оператор присваивания.

Правило 46: Определяйте внутри шаблонов функции, не являющиеся членами, когда желательны преобразования типа

В правиле 24 объясняется, почему только к свободным функциям применяются неявные преобразования типов всех аргументов. В качестве примера была приведена функция operator* для класса Rational. Прежде чем продолжить чтение, рекомендую вам освежить этот пример в памяти, потому что сейчас мы вернемся к этой теме, рассмотрев безобидные, на первый взгляд, модификации примера из правила 24. Отличие только в том, что и класс Rational, и operator* в нем сделаны шаблонами:

template <typename T>

class Rational {

public:

Rational(const T& numerator = 0, // см. в правиле 20 – почему

const T& denominator = 1); // параметр передается по ссылке

const T numerator const; // см. в правиле 28 – почему

const T denominator const; // результат возвращается по

... // значению, а в правиле 3 –

// почему они константны

};

template <typename T>

const Rational<T> operator*(const Rational<T>& lhs,

const Rational<T>& rhs)

{...}

Как и в правиле 24, мы собираемся поддерживать смешанную арифметику, поэтому хотелось бы, чтобы приведенный ниже код компилировался. Мы не ожидаем подвохов, потому что аналогичный код в правиле 24 работал. Единственное отличие в том, что класс Rational и функция-член operator* теперь шаблоны:

Raional<int> oneHalf(1, 2); // это пример из правила 24,

// но Rational – теперь шаблон

Ratinal<int> result = oneHalf * 2; // ошибка! Не компилируется

Тот факт, что этот код не компилируется, наводит на мысль, что в шаблоне Rational есть нечто, отличающее его от нешаблонной версии. И это на самом деле так. В правиле 24 компилятор знал, какую функцию мы пытаемся вызвать (operator*, принимающую два параметра типа Rational), здесь же ему об этом ничего не известно. Поэтому компилятор пытается решить, какую функцию нужно конкретизировать (то есть создать) из шаблона operator*. Он знает, что имя этой функции operator* и она принимает два параметра типа Rational<T>, но для того чтобы произвести конкретизацию, нужно выяснить, что такое T. Проблема в том, что компилятор не может этого сделать.

Пытаясь вывести T, компилятор смотрит на типы аргументов, переданных при вызове operator*. В данном случае это Rational<int> (тип переменной oneHalf) и int (тип литерала 2). Каждый параметр рассматривается отдельно.

Вывод на основе типа oneHalf сделать легко. Первый параметр operator* объявлен как Rational<T>, а первый аргумент, переданный operator* (oneHalf), имеет тип Rational<int>, поэтому T должен быть int. К сожалению, вывести тип другого параметра не так просто. Из объявления известно, что тип второго параметра operator* равен Rational<T>, но второй аргумент, переданный функции operator* (число 2), имеет тип int. Как компилятору определить, что есть T в данном случае? Можно ожидать, что он воспользутся не-explicit конструктором, чтобы преобразовать 2 в Rational<int> и таким образом сделать вывод, что T есть int, но на деле этого не происходит. Компилятор не поступает так потому, что функции неявного преобразования типа никогда не рассматриваются при выводе аргументов шаблона. Никогда. Да, такие преобразования используются при вызовах функций, но перед тем, как вызывать функцию, нужно убедиться, что она существуют. Чтобы убедиться в этом, необходимо вывести типы параметров для всех потенциально подходящих шаблонов функций (чтобы можно было конкретизировать правильную функцию). Но неявные преобразования типов посредством вызова конструкторов при выводе аргументов шаблона не рассматриваются. В правиле 24 никаких шаблонов не было, поэтому и проблема вывода аргументов шаблона не возникала. Здесь же мы имеем дело с шаблонной частью C++ (см. правило 1), и она выходит на первый план.

Мы можем помочь компилятору в выводе аргументов шаблона, воспользовавшись объявлением дружественной функции в шаблонном классе. Это означает, что класс Rational<T> может объявить operator* для Rational<T> как функцию-друга. К шаблонам классов процедура вывода аргументов не имеет отношения (она применяется только к шаблонам функций), поэтому тип T всегда известен в момент конкретизации Rational<T>. Это упрощает объявление соответствующей функции operator* как друга класса Rational<T>:

template <typename T>

class Rational {

public:

...

friend // объявление функции

const Rational operator*(const Rational& lhs, // operator*

const Rational& rhs); // (подробности см. ниже)

};

template <typename T> // определение функции

const Rational<T> operator*(const Rational<T>& lhs, // operator*

  • Читать дальше
  • 1
  • ...
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • ...

Ебукер (ebooker) – онлайн-библиотека на русском языке. Книги доступны онлайн, без утомительной регистрации. Огромный выбор и удобный дизайн, позволяющий читать без проблем. Добавляйте сайт в закладки! Все произведения загружаются пользователями: если считаете, что ваши авторские права нарушены – используйте форму обратной связи.

Полезные ссылки

  • Моя полка

Контакты

  • chitat.ebooker@gmail.com

Подпишитесь на рассылку: