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

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

Жанры

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

Майерс Скотт

Шрифт:
empty-line/>

Rational oneEighth(1, 8);

Rational one Half(1, 2);

Rational result = oneHalf * oneEighth; // правильно

result = result * oneEighth; // правильно

Но вы не удовлетворены. Хотелось бы поддерживать также смешанные операции, чтобы Rational можно было умножить, например, на int. В конце концов, это довольно естественно – иметь возможность перемножать два числа, даже если они принадлежат к разным числовым типам.

Однако если вы

попытаетесь выполнить смешанные арифметические операции, то обнаружите, что они работают только в половине случаев:

result = oneHalf * 2; // правильно

result = 2 * oneHalf; // ошибка!

Это плохой знак. Умножение должно быть коммутативным (не зависеть от порядка сомножителей), помните?

Источник проблемы становится понятным, если переписать два последних выражения в функциональной форме:

result = oneHalf.operator*(2); // правильно

result = 2.operator*(oneHalf); // ошибка!

Объект oneHalf – это экземпляр класса, включающего в себя operator*, поэтому компилятор вызывает эту функцию. Но с целым числом 2 не ассоциирован никакой класс, а значит, нет для него и функции operator*. Компилятор будет также искать функции operator*, не являющиеся членами класса (в текущем пространстве имен или в глобальной области видимости):

result = operator*(2, oneHalf); // ошибка!

Но в данном случае нет и свободной функции operator*, которая принимала бы аргументы int и Rational, поэтому поиск завершится ничем.

Посмотрим еще раз на успешный вызов. Видите, что второй параметр – целое число 2, хотя Rational::operator* принимает в качестве аргумента объект Rational. Что происходит? Почему 2 работает в одной позиции и не работает в другой?

Происходит неявное преобразование типа. Компилятор знает, что вы передали int, а функция требует Rational, но он также знает, что можно получить подходящий объект, если вызвать конструктор Rational c переданным вами аргументом int. Так он и поступает. Иными словами, компилятор трактует показанный выше вызов, как если бы он был написан примерно так:

const Rational temp(2); // создать временный объект Rational из 2

result = oneHalf * temp; // то же, что oneHalf.operator*(temp);

Конечно, компилятор делает это только потому, что есть конструктор, объявленный без квалификатора explicit. Если бы квалификатор explicit присутствовал, то ни одно из следующих предложений не скомпилировалось бы:

result = oneHalf * 2; // ошибка! (при наличии explicit-конструктора):

// невозможно преобразовать 2 в Ratinal

result = 2 * oneHalf; // та же ошибка, та же проблема

Со смешанной арифметикой при таком подходе придется распроститься, но, по крайней мере, такое поведение

непротиворечиво.

Ваша цель, однако, – обеспечить и согласованность, и поддержку смешанной арифметики, то есть нужно найти такое решение, при котором оба предложения компилируются. Это возвращает нас к вопросу о том, почему даже при наличии explicit-конструктора в классе Rational одно из них компилируется, а другое – нет:

result = oneHalf * 2; // правильно (при не explicit-конструкторе)

result = 2 * oneHalf; // ошибка! (даже при не explicit-конструкторе)

Оказывается, что к параметрам применимы неявные преобразования, только если они перечислены в списке параметров. Неявный параметр, соответствующий объекту, чья функция-член вызывается (тот, на который указывает this), никогда не подвергается неявному преобразованию. Вот почему первый вызов компилируется, а второй – нет. В первом случае параметр указан в списке параметров функции, а во втором – нет.

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

class Rational {

... // не содержит operator*

};

const Rational operator*(const Rational& lhs, // теперь свободная функция

const Rational& rhs)

{

return Rational(lhs.numerator * rhs.numerator,

lhs.denominator * rhs.denominator);

}

Rational oneFourth(1, 4);

Rational result;

result = oneFourth * 2; // правильно

result = 2 * oneFourth; // ура, работает!

Это можно было бы назвать счастливым концом, если бы не одно «но». Должен ли operator* быть другом класса Rational?

В данном случае ответом будет «нет», потому что operator* может быть реализован полностью в терминах открытого интерфейса Rational. Приведенный выше код показывает, как это можно сделать. И мы приходим к важному выводу: противоположностью функции-члена является свободная функция, а функция – друг класса. Многие программисты на C++ полагают, что раз функция имеет отношение к классу и не должна быть его членом (например, из-за необходимости преобразовывать типы всех аргументов), то она должна быть другом. Этот пример показывает, что такое предположение неправильно. Если вы можете избежать назначения функции другом класса, то должны так и поступить, потому что, как и в реальной жизни, друзья часто доставляют больше хлопот, чем хотелось бы. Конечно, иногда отношения дружественности оправданы, но факт остается фактом: если функция не должна быть членом, это не означает автоматически, что она должна быть другом.

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

Моя на одну ночь

Тоцка Тала
Любовные романы:
современные любовные романы
короткие любовные романы
5.50
рейтинг книги
Моя на одну ночь

Черный Маг Императора 8

Герда Александр
8. Черный маг императора
Фантастика:
юмористическое фэнтези
попаданцы
аниме
5.00
рейтинг книги
Черный Маг Императора 8

Измена. Отбор для предателя

Лаврова Алиса
1. Отбор для предателя
Фантастика:
фэнтези
5.00
рейтинг книги
Измена. Отбор для предателя

Кодекс Крови. Книга II

Борзых М.
2. РОС: Кодекс Крови
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Кодекс Крови. Книга II

Шаг в бездну

Муравьёв Константин Николаевич
3. Перешагнуть пропасть
Фантастика:
фэнтези
космическая фантастика
7.89
рейтинг книги
Шаг в бездну

Часовая битва

Щерба Наталья Васильевна
6. Часодеи
Детские:
детская фантастика
9.38
рейтинг книги
Часовая битва

Вечная Война. Книга II

Винокуров Юрий
2. Вечная война.
Фантастика:
юмористическая фантастика
космическая фантастика
8.37
рейтинг книги
Вечная Война. Книга II

Хроники странного королевства. Вторжение. (Дилогия)

Панкеева Оксана Петровна
110. В одном томе
Фантастика:
фэнтези
9.38
рейтинг книги
Хроники странного королевства. Вторжение. (Дилогия)

Часовой ключ

Щерба Наталья Васильевна
1. Часодеи
Фантастика:
фэнтези
9.36
рейтинг книги
Часовой ключ

Инвестиго, из медика в маги

Рэд Илья
1. Инвестиго
Фантастика:
фэнтези
городское фэнтези
попаданцы
5.00
рейтинг книги
Инвестиго, из медика в маги

Кротовский, может, хватит?

Парсиев Дмитрий
3. РОС: Изнанка Империи
Фантастика:
попаданцы
альтернативная история
аниме
7.50
рейтинг книги
Кротовский, может, хватит?

Драконий подарок

Суббота Светлана
1. Королевская академия Драко
Любовные романы:
любовно-фантастические романы
7.30
рейтинг книги
Драконий подарок

Очешуеть! Я - жена дракона?!

Амеличева Елена
Фантастика:
юмористическая фантастика
5.43
рейтинг книги
Очешуеть! Я - жена дракона?!

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

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