Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
По прежним версиям стандарта перед классами с преобразованием в тип bool стояла проблема: поскольку тип bool арифметический, объект этого типа, допускающего преобразование в тип bool, применим в любом контексте, где ожидается арифметический тип. Такие преобразования могут происходить весьма удивительными способами. В частности, если бы у класса istream было преобразование в тип bool, то следующий код вполне компилировался бы:
int i = 42;
cin << i; // этот код был бы допустим, если бы преобразование
// в тип bool не было явным!
Эта программа пытается использовать оператор вывода для входного потока. Для класса istream оператор << не определен, поэтому такой код безусловно ошибочен. Но этот код мог бы использовать оператор преобразования в тип bool, чтобы преобразовать объект cin в bool. Полученное значение типа bool было бы затем преобразовано в тип int, который вполне применим как левый операнд встроенной версии оператора сдвига влево. В результате преобразованное значение типа bool (1 или 0) было бы сдвинуто влево на 42 позиции.
Явный оператор преобразованияЧтобы предотвратить подобные проблемы, новый стандарт вводит явный оператор преобразования (explicit conversion operator):
class SmallInt { public:
// компилятор не будет автоматически применять это преобразование
explicit operator int() const { return val; }
// другие члены как прежде
};
Подобно явным конструкторам (см. раздел 7.5.4), компилятор не будет (обычно) использовать явный оператор преобразования для неявных преобразований:
SmallInt si = 3; // ok: конструктор класса SmallInt не является явным
si + 3; // ошибка: нужно неявное преобразование, но оператор int
// является явным
static_cast<int>(si) + 3; // ok: явный запрос преобразования
Если оператор преобразования является явным, такое преобразование вполне можно осуществить. Но за одним исключением такое приведение следует осуществить явно.
Исключение состоит в том, что компилятор применит явное преобразование в выражении, используемом как условие. Таким образом, явное преобразование будет использовано неявно для преобразования выражения, используемого как:
• условие оператора if, while или do;
• выражение условия в заголовке оператора for;
• операнд логического оператора NOT (!), OR (||) или AND (&&);
• выражение условия в условном операторе (?:).
Преобразование в тип boolВ прежних версиях библиотеки типы ввода-вывода определяли преобразование в тип void*. Это было сделано во избежание проблем, описанных выше. По новому стандарту библиотека ввода-вывода определяет вместо этого явное преобразование в тип bool.
Всякий раз, когда потоковый объект используется в условии, применяется оператор operator bool(), определенный для типов ввода-вывода. Например:
while (std::cin >> value)
Условие в операторе while выполняет оператор ввода, который читает в переменную value и возвращает объект cin. Для обработки условия объект cin неявно преобразуется функцией преобразования istream::operator bool(). Эта функция возвращает значение true, если флагом состояния потока cin является good (см. раздел 8.1.2), и false в противном случае.
Преобразование в тип bool обычно используется в условиях. В результате оператор operator bool обычно должен определяться как явный.
Упражнения раздела 14.9.1Упражнение 14.45. Напишите операторы преобразования для преобразования объекта класса Sales_data в значения типа string и double. Какие значения, по-вашему, должны возвращать эти операторы?
Упражнение 14.46. Объясните, является ли определение этих операторов преобразования класса Sales_data хорошей идеей и должны ли они быть явными.
Упражнение 14.47. Объясните различие между этими двумя операторами преобразования:
struct Integral {
operator const int();
operator int() const;
};
Упражнение 14.48. Должен ли класс из упражнения 7.40 раздела 7.5.1 использовать преобразование в тип bool. Если да, то объясните почему и укажите, должен ли оператор быть явным. В противном случае объясните, почему нет.
Упражнение 14.49. Независимо от того, хороша ли эта идея, определите преобразование в тип bool для класса из предыдущего упражнения.
14.9.2. Избегайте неоднозначных преобразований
Если у класса есть один или несколько операторов преобразования, важно гарантировать наличие только одного способа преобразования из типа класса в необходимый тип. Если будет больше одного способа осуществления преобразования, то будет весьма затруднительно написать однозначный код.
Есть два случая, когда возникает несколько путей осуществления преобразования. Первый — когда два класса обеспечивают взаимное преобразование. Например, взаимное преобразование осуществляется тогда, когда класс А определяет конструктор преобразования, получающий объект класса B, а класс в определяет оператор преобразования в тип А.
Второй случай возникновения нескольких путей преобразования — определение нескольких преобразований в и из типов, которые сами связаны преобразованиями. Самый очевидный пример — встроенные арифметические типы. Каждый класс обычно должен определять не больше одного преобразования в или из арифметического типа.
Обычно не следует определять классы со взаимными преобразованиями или определять преобразования в или из арифметических типов.
Распознавание аргумента и взаимные преобразованияВ следующем примере определены два способа получения объекта класса А из В: либо при помощи оператора преобразования класса В, либо при помощи конструктора класса А, получающего объект класса В:
// обычно взаимное преобразование между двумя типами - плохая идея
struct B;
struct А {
А() = default;
A(const В&); // преобразует В в A
// другие члены
};
struct В {
operator A() const; // тоже преобразует В в A
// другие члены
};
A f (const A&);
A a = f(b); // ошибка неоднозначности: f(B::operator A())
// или f(A::A(const B&))
Поскольку существуют два способа получения объекта класса А из В, компилятор не знает, какой из них использовать; поэтому вызов функции f() неоднозначен. Для получения объекта класса В этот вызов может использовать конструктор класса А или оператор преобразования класса В, преобразующий объект класса В в А. Поскольку обе эти функции одинаково хороши, вызов неоднозначен и ошибочен.
Если этот вызов необходим, оператор преобразования или конструктор следует вызвать явно:
A a1 = f(b.operator А()); // ok: использовать оператор преобразования В
A а2 = f(A(b)); // ok: использовать конструктор класса А
Обратите внимание: нельзя решить неоднозначность при помощи приведения — у самого приведения будет та же двусмысленность.
Двусмысленность и множественность путей преобразования во встроенные типыДвусмысленность возникает также в случае, когда класс определяет несколько преобразований в (или из) типы, которые сами связываются преобразованиями. Самый простой и наглядный пример (а также особенно проблематичный) — это когда класс определяет конструкторы преобразования в или из более, чем один арифметический тип.
Например, у следующего класса есть конструкторы преобразования из двух разных арифметических типов и операторы преобразования в два разных арифметических типа:
struct A {
A(int = 0); // обычно плохая идея иметь два
A(double); // преобразования из арифметических типов
operator int() const; // обычно плохая идея иметь два
operator double() const; // преобразования в арифметические типы
// другие члены
};
void f2(long double);
A a;