класса tr1::shared_ptr, который иллюстрирует это положение:

template<class T> class shared_ptr {

public:

shared_ptr(shared_ptr const& r); // конструктор копирования

template<class Y> // обобщенный

shared_ptr(shared_ptr<Y> const& r); // конструктор копирования

shared_ptr& operator=(shared_ptr const& r); // оператор присваивания

template<class Y> // обобщенный оператор

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*

const Rational<T>& rhs)

{...}

Теперь вызовы operator* с аргументами разных типов скомпилируются, потому что при объявлении объект oneHalf типа Rational<int> конкретизируется класс Rational<int> и вместе с ним функция-друг operator*, которая принимает параметры Rational<int>. Поскольку объявляется функция (а не шаблон функции), компилятор может для вывода типов параметров пользоваться функциями неявного преобразования (например, не-explicit конструкторами Rational) и, стало быть, сумеет разобраться в вызове operator* с параметрами разных типов.

К сожалению, фраза «сумеет разобраться» в данном контексте имеет иронический оттенок, поскольку хотя код и компилируется, но не компонуется. Вскоре мы займемся этой проблемой, но сначала я хочу сделать одно замечание о синтаксисе, используемом для объявления функции operator* в классе Rational.

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

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

Отметить Добавить цитату