При наличии преобразования в одну сторону естественно иметь возможность выполнять и обратное преобразование:
TMoney -> long double
С++ позволяет это сделать: мы должны определить в классе функцию-метод преобразования, как показано в листинге 3.14.
Листинг 3.14. Функция преобразования
operator long doubleO const { return Summa/100; }
В данном случае мы осуществили перегрузку функции-операции (см. п. п. 12.3.2 в [1]). Тогда становятся возможными присвоения объектов типа TMoney переменным встроенного типа long double, например:
int main()
{ TMoney t = 2.67,
s = TMoney(1234.56);
long double ww = s; // присвоили деньги
cout << ww << endl;
ww = t; // присвоили деньги
cout << ww << endl; return 0;
}
Аналогичные неявные преобразования выполняются при передаче параметров в функцию и при возврате результата. Пусть у нас определены функции:
TMoney fl(const TMoney &a) { return a; }
long double f2(const TMoney &a) { return a; }
Тогда допускаются вызовы:
TMoney t = f1(2.67); long double ww = f2(t);
В первом случае осуществляется неявное преобразование целого числа 2.67 в тип TMoney конструктором, и переменная t получает значение 2 рубля 67 копеек.
А во втором случае неявное преобразование в дробное число выполняется при возврате, и переменной w присваивается значение 2.67.
Общий вид функции преобразования следующий:
operator type();
Здесь type может быть встроенным типом, типом класса или именем typedef. На месте type не может стоять тип массива или функции. Функция преобразования обязательно должна быть методом. В объявлении не должны задаваться ни тип возвращаемого значения, ни список параметров — это будет ошибкой.
Неявное преобразование можно реализовать для любых типов. Пусть у нас определен некоторый класс X. Тогда в определении класса Y мы можем определить неявное преобразование типов:
х -> Y
Y -> X
Это показано в листинге 3.15.
Листинг 3.15. Неявное преобразование типов произвольного вида class Y {
Y(X,a){ /*...*/} // К -> Y
operator X() {/* ... */}: // Y -> X
};
Первое преобразование делает конструктор инициализации, а второе — функция-операция приведения типа. В результате объекты типа Y можно инициализировать объектами типа X, разрешается смешивать без указания явных преобразований объекты типов X и Y в одном выражении.
Опытные программисты знают, что определение неявных преобразований часто приводит к неприятным сюрпризам. И это хорошо, если неприятности выявляются при трансляции. Например, для нашего класса ТМопеу с определенным ранее преобразованием в long double вдруг выясняется, что выражения, в которых участвуют денежные суммы и дробные константы, являются неоднозначными. Например:
long double ww = s+1; // ошибка трансляции
Этот оператор в системе Visual C++.NET вызывает ошибку трансляции:
error С2666: 'ТМопеу::operator*' : 2 overloads have similar conversions
Это сообщение означает, что компилятор не может выбрать, какое из преобразований применять при сложении: то ли привести константу 1 к типу ТМопеу, то ли s привести к типу long double. Заметьте, что операции сложения денег и констант не определены, тем не менее ошибка возникает.
СОВЕТ
Объявляйте функции преобразования только тогда, когда без них совершенно невозможно обойтись. Иначе рискуете нарваться на очень «хитрые» ошибки.
Запрет неявных преобразований
Таким образом, с определением преобразований по умолчанию надо быть осторожным. Лучше даже запретить их совсем, чтобы заставить клиента явно обозначать свои намерения при преобразованиях типов. Запретить следующее преобразование легко — достаточно просто не определять функцию-операцию преобразования:
TMoney -> long double
Но С++ позволяет запретить неявные преобразования и конструктором. Для этого в языке существует специальное ключевое слово expli ci t (см. п. п. 7.1.2/6 в [1]). Заголовок конструктора инициализации должен быть таким:
explicit TMoney(const long double &t=0.0); // конструктор
Тогда допускается только явный вызов конструктора как при объявлении с инициализацией, так и при передаче параметра:
TMoney t = fl(TMoney(2.67)); TMoney s = TMoney(12.34);
При попытках не писать справа от знака присваивания тип TMoney возникают ошибки трансляции:
TMoney р = 2.50; // ошибка трансляции
Однако следующая запись не является запрещенной, так как представляет собой явный вызов конструктора:
TMoney р(2.50);
|