Скотт Майерс - Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ
- Название:Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ
- Автор:
- Жанр:
- Издательство:Литагент «ДМК»233a80b4-1212-102e-b479-a360f6b39df7
- Год:2006
- Город:Москва
- ISBN:5-94074-304-8
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Скотт Майерс - Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ краткое содержание
Эта книга представляет собой перевод третьего издания американского бестселлера Effective C++ и является руководством по грамотному использованию языка C++. Она поможет сделать ваши программы более понятными, простыми в сопровождении и эффективными. Помимо материала, описывающего общую стратегию проектирования, книга включает в себя главы по программированию с применением шаблонов и по управлению ресурсами, а также множество советов, которые позволят усовершенствовать ваши программы и сделать работу более интересной и творческой. Книга также включает новый материал по принципам обработки исключений, паттернам проектирования и библиотечным средствам.
Издание ориентировано на программистов, знакомых с основами C++ и имеющих навыки его практического применения.
Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
const Rational& rhs); // (подробности см. ниже)
};
template // определение функции
const Rational operator*(const Rational& lhs, // operator*
const Rational& rhs)
{...}
Теперь вызовы operator* с аргументами разных типов скомпилируются, потому что при объявлении объект oneHalf типа Rational конкретизируется класс Rational и вместе с ним функция-друг operator*, которая принимает параметры Rational. Поскольку объявляется функция (а не шаблон функции), компилятор может для вывода типов параметров пользоваться функциями неявного преобразования (например, не-explicit конструкторами Rational) и, стало быть, сумеет разобраться в вызове operator* с параметрами разных типов.
К сожалению, фраза «сумеет разобраться» в данном контексте имеет иронический оттенок, поскольку хотя код и компилируется, но не компонуется. Вскоре мы займемся этой проблемой, но сначала я хочу сделать одно замечание о синтаксисе, используемом для объявления функции operator* в классе Rational.
Внутри шаблона класса имя шаблона можно использовать как сокращенное обозначение шаблона вместе с параметрами, поэтому внутри Ratonal разрешается писать просто Rational вместо Ratonal. В данном примере это экономит лишь несколько символов, но когда есть несколько параметров с длинными именами, это помогает уменьшить размер исходного кода и одновременно сделать его яснее. Я вспомнил об этом, потому что operator* объявлен как принимающий и возвращающий Rational вместо Rational. Также корректно было бы объявить operator* следующим образом:
template
class Rational {
public:
...
friend
const Rational operator*(const Rational& lhs,
const Rational& rhs);
...
};
Однако проще (и часто так и делается) использовать сокращенную форму.
Теперь вернемся к проблеме компоновки. Код, содержащий вызов с параметрами различных типов, компилируется, потому что компилятор знает, что мы хотим вызвать вполне определенную функцию (operator*, принимающую параметры типа Rational и Rational), но эта функция только объявлена внутри Rational, но не определена там. Наша цель – заставить шаблон функции operator*, не являющейся членом класса, предоставить это определение, но таким образом ее не достичь. Если мы объявляем функцию самостоятельно (а так и происходит, когда она находится внутри шаблона Rational), то должны позаботиться и об ее определении. В данном случае мы нигде не привели определения, поэтому компоновщик его и не находит.
Простейший способ исправить ситуацию – объединить тело operator* с его объявлением:
template
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 class Ratonal; // объявление
// шаблона Rational
template // объявление
const Rational doMultiply(const Rational& lhs, // шаблона
const Rational& rhs); // вспомогательной
// функции
template
class Rational {
public:
...
friend
const Rational operator*( const Rational& lhs,
const Rational& rhs) // друг объявляет
{ return doMultiply(lhs, rhs};} // вспомогательную
... // функцию
};
Многие компиляторы требуют, чтобы все определения шаблонов находились в заголовочных файлах, поэтому может понадобиться определить в заголовке еще и функцию doMultiply. Как объясняется в правиле 30, такие шаблоны не обязаны быть встроенными. Вот как это может выглядеть:
template // определение шаблона
const Rational doMultiply( const Rational& lhs, // вспомогательной
const Rational& rhs) // функции
{ // в заголовочном файле
return Rational(lhs.numerator() * rhs.numerator(), // при необходимости
lhs.denominator () * rhs.denominator());
}
Конечно, будучи шаблоном, doMultiply не поддерживает умножения значений разного типа, но ей это и не нужно. Она вызывается только из operator*, который обеспечивает поддержку параметров смешанного типа! По существу, функция operator* поддерживает любые преобразования типа, необходимые для перемножения объектов класса Rational, а затем передает эти два объекта соответствующей конкретизации шаблона doMultiply, которая и выполняет собственно операцию умножения. Кооперация в действии, не так ли?
• Когда вы пишете шаблон класса, в котором есть функции, нуждающиеся в неявных преобразованиях типа для всех параметров, определяйте такие функции как друзей внутри шаблона класса.
Правило 47: Используйте классы-характеристики для предоставления информации о типах
В основном библиотека STL содержит шаблоны контейнеров, итераторов и алгоритмов, но есть в ней и некоторые служебные шаблоны. Один из них называется advance. Шаблон advance перемещает указанный итератор на заданное расстояние:
Читать дальшеИнтервал:
Закладка: