Чтение онлайн

на главную - закладки

Жанры

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

Майерс Скотт

Шрифт:

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

Внутри шаблона класса имя шаблона можно использовать как сокращенное обозначение шаблона вместе с параметрами, поэтому внутри Ratonal<T> разрешается писать просто Rational вместо Ratonal<T>. В данном примере это экономит лишь несколько символов, но когда есть несколько параметров с длинными именами, это помогает уменьшить размер исходного кода и одновременно сделать его яснее. Я вспомнил об этом, потому что operator* объявлен как принимающий и возвращающий Rational вместо Rational<T>. Также корректно было бы объявить operator* следующим образом:

template <typename T>

class Rational {

public:

...

friend

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

const Rational<T>& rhs);

...

};

Однако проще (и часто так и делается) использовать сокращенную форму.

Теперь вернемся к проблеме компоновки. Код, содержащий вызов с параметрами различных типов, компилируется, потому что компилятор знает, что мы хотим вызвать вполне определенную функцию (operator*, принимающую параметры типа Rational<int> и Rational<int>), но эта функция только объявлена внутри Rational, но не определена там. Наша цель – заставить шаблон функции operator*, не являющейся членом класса, предоставить это определение, но таким образом ее не достичь. Если мы объявляем функцию самостоятельно (а так и происходит, когда она находится внутри шаблона Rational), то должны позаботиться и об ее определении. В данном случае мы нигде не привели определения, поэтому компоновщик его и не находит.

Простейший способ исправить ситуацию – объединить тело operator* с его объявлением:

template <typename T>

class Rational {

public:

...

friend Rational operator*(const Rational& lhs, const Rational& rhs)

{

return Rational(lhs.numerator * rhs.numerator, // та же

lhs.denominator * rhs.denominator); // реализация,

} // что и

// в правиле 24

};

Наконец-то все работает как нужно: вызовы operator* с параметрами смешанных типов компилируются, компонуются и запускаются. Ура!

Интересное наблюдение, касающееся этой техники: использование отношения дружественности никак не связано с желанием получить доступ к закрытой части класса. Чтобы сделать возможными преобразования типа для всех аргументов, нам нужна функция, не являющаяся членом (см. правило 24); а для того чтобы получить автоматическую конкретизацию правильной функции, нам нужно объявить ее внутри класса. Единственный способ объявить свободную функцию внутри класса – сделать ее другом (friend). Что мы и делаем. Необычно? Да. Эффективно? Вне всяких сомнений.

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

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

template <typename T> class Ratonal; // объявление

// шаблона Rational

template <typename T> // объявление

const Rational<T> doMultiply(const Rational<T>& lhs, // шаблона

const Rational<T>& rhs); // вспомогательной

// функции

template <typename T>

class Rational {

public:

...

friend

const Rational operator*( const Rational& lhs,

const Rational& rhs) // друг объявляет

{ return doMultiply(lhs, rhs};} // вспомогательную

... // функцию

};

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

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

Особое назначение

Тесленок Кирилл Геннадьевич
2. Гарем вне закона
Фантастика:
фэнтези
6.89
рейтинг книги
Особое назначение

Её (мой) ребенок

Рам Янка
Любовные романы:
современные любовные романы
6.91
рейтинг книги
Её (мой) ребенок

Я Гордый часть 2

Машуков Тимур
2. Стальные яйца
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Я Гордый часть 2

Адепт. Том второй. Каникулы

Бубела Олег Николаевич
7. Совсем не герой
Фантастика:
фэнтези
попаданцы
9.05
рейтинг книги
Адепт. Том второй. Каникулы

Императорский отбор

Свободина Виктория
Фантастика:
фэнтези
8.56
рейтинг книги
Императорский отбор

Законы Рода. Том 6

Flow Ascold
6. Граф Берестьев
Фантастика:
юмористическое фэнтези
аниме
5.00
рейтинг книги
Законы Рода. Том 6

Начальник милиции. Книга 3

Дамиров Рафаэль
3. Начальник милиции
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Начальник милиции. Книга 3

Огненный князь 3

Машуков Тимур
3. Багряный восход
Фантастика:
фэнтези
боевая фантастика
попаданцы
5.00
рейтинг книги
Огненный князь 3

Восход. Солнцев. Книга VI

Скабер Артемий
6. Голос Бога
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Восход. Солнцев. Книга VI

Страж. Тетралогия

Пехов Алексей Юрьевич
Страж
Фантастика:
фэнтези
9.11
рейтинг книги
Страж. Тетралогия

Мастер 7

Чащин Валерий
7. Мастер
Фантастика:
фэнтези
боевая фантастика
попаданцы
технофэнтези
аниме
5.00
рейтинг книги
Мастер 7

Волк 5: Лихие 90-е

Киров Никита
5. Волков
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Волк 5: Лихие 90-е

Сломанная кукла

Рам Янка
5. Серьёзные мальчики в форме
Любовные романы:
современные любовные романы
5.00
рейтинг книги
Сломанная кукла

Идеальный мир для Лекаря 18

Сапфир Олег
18. Лекарь
Фантастика:
юмористическое фэнтези
аниме
5.00
рейтинг книги
Идеальный мир для Лекаря 18