Неоднозначность
Наличие в одном и том же классе конвертеров, выполняющих неявные преобразования во встроенные типы, и перегруженных операторов может приводить к неоднозначности при выборе между ними. Например, есть следующее определение класса String с функцией сравнения:
class String { // ... public: String( const char * = 0 ); bool operator== ( const String & ) const; // нет оператора operator== ( const char * ) |
};
и такое использование оператора operator==:
String flower( "tulip" ); void foo( const char *pf ) { // вызывается перегруженный оператор String::operator==() if ( flower == pf ) cout << pf << " is a flower!\en"; // ... |
}
Тогда при сравнении
flower == pf
вызывается оператор равенства класса String:
String::operator==( const String & ) const;
Для трансформации правого операнда pf из типа const char* в тип String
параметра operator==()
применяется определенное пользователем преобразование, которое вызывает конструктор:
String( const char * )
Если добавить в определение класса String
конвертер в тип const char*:
class String { // ... public: String( const char * = 0 ); bool operator== ( const String & ) const; operator const char*(); // новый конвертер |
};
то показанное использование operator==()
становится неоднозначным:
// проверка на равенство больше не компилируется! |
if (flower == pf)
Из-за добавления конвертера operator const
char*()
встроенный оператор сравнения
bool operator==( const char *, const char * )
тоже считается устоявшей функцией. С его помощью левый операнд flower
типа String
может быть преобразован в тип const char *.
Теперь для использования operator==() в foo()
есть две устоявших операторных функции. Первая из них
String::operator==( const String & ) const;
требует применения определенного пользователем преобразования правого операнда pf из типа const char* в тип String. Вторая
bool operator==( const char *, const char * )
требует применения пользовательского преобразования левого операнда flower из типа String в тип const char*.
Таким образом, первая устоявшая функция лучше для левого операнда, а вторая– для правого. Поскольку наилучшей функции не существует, то вызов помечается компилятором как неоднозначный.
При проектировании интерфейса класса, включающего объявление перегруженных операторов, конструкторов и конвертеров, следует быть весьма аккуратным. Определенные пользователем преобразования применяются компилятором неявно. Это может привести к тому, что встроенные операторы окажутся устоявшими при разрешении перегрузки для операторов с операндами типа класса.
Упражнение 15.17
Назовите пять множеств функций-кандидатов, рассматриваемых при разрешении перегрузки оператора с операндами типа класса.
Упражнение 15.18
Какой из операторов operator+()
будет выбран в качестве наилучшего из устоявших для оператора сложения в main()? Перечислите все функции-кандидаты, все устоявшие функции и преобразования типов, которые надо применить к аргументам для каждой устоявшей функции.
namespace NS { class complex { complex( double ); // ... }; class LongDouble { friend LongDouble operator+( LongDouble &, int ) { /* ... */ } public: LongDouble( int ); operator double(); LongDouble operator+( const complex & ); // ... }; LongDouble operator+( const LongDouble &, double ); } int main() { NS::LongDouble ld(16.08); double res = ld + 15.05; // какой operator+? return 0; |
16