Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
85
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

772

 

третье множество содержит операторы, объявленные друзьями класса

 

 

 

SmallInt. Сюда входит

 

 

NS::SmallInt NS::operator+( const SmallInt &, int );

 

 

четвертое множество содержит операторы, объявленные членами SmallInt.

 

 

 

 

 

Такой тоже есть:

 

 

NS::SmallInt NS::SmallInt::operator+( const myFloat & );

 

 

 

 

int operator+( int, int );

 

 

 

 

 

 

double operator+( double, double );

 

 

T* operator+( T*, I );

 

 

пятое множество содержит встроенные бинарные операторы:

 

T* operator+( I, T* );

Да, формирование множества кандидатов для разрешения оператора, использованного с применением операторного синтаксиса, утомительно. Но после того как оно построено, устоявшие функции и наилучшая из них находятся, как и прежде, путем анализа преобразований, применимых к операндам отобранных кандидатов.

15.12.2. Устоявшие функции

Множество устоявших операторных функций формируется из множества кандидатов путем отбора лишь тех операторов, которые могут быть вызваны с заданными операндами. Например, какие из семи найденных выше кандидатов устоят? Оператор

NS::SmallInt si(15);

использован в следующем контексте: si + 5.66;

Левый операнд имеет тип SmallInt, а правый double.

Первый кандидат является устоявшей функцией для данного использования operator+():

NS::SmallInt NS::operator+( const SmallInt &, double );

Левый операнд типа SmallInt в качестве инициализатора точно соответствует формальному параметру-ссылке этого перегруженного оператора. Правый, имеющий тип double, также точно соответствует второму формальному параметру.

Следующая функция-кандидат также устоит:

NS::SmallInt NS::operator+( const SmallInt &, int );

Левый операнд si типа SmallInt в качестве инициализатора точно соответствует формальному параметру-ссылке перегруженного оператора. Правый имеет тип int и

С++ для начинающих

773

может быть приведен к типу второго формального параметра с помощью стандартного преобразования.

Устоит и третья функция-кандидат:

NS::SmallInt NS::SmallInt::operator+( const myFloat & );

Левый операнд si имеет тип SmallInt, т.е. тип того класса, членом которого является перегруженный оператор. Правый имеет тип int и приводится к типу класса myFloat с

помощью определенного пользователем преобразования в виде конструктора myFloat(double).

int operator+( int, int );

Четвертой и пятой устоявшими функциями являются встроенные операторы: double operator+( double, double );

Класс SmallInt содержит конвертер, который может привести значение типа SmallInt к типу int. Этот конвертер используется вместе с первым встроенным оператором для преобразования левого операнда в тип int. Второй операнд типа double трансформируется в тип int с помощью стандартного преобразования. Что касается второго встроенного оператора, то конвертер приводит левый операнд от типа SmallInt к типу int, после чего результат стандартно преобразуется в double. Второй же операнд типа double точно соответствует второму параметру.

Лучшей из этих пяти устоявших функций является первая, operator+(), объявленная в пространстве имен NS:

NS::SmallInt NS::operator+( const SmallInt &, double );

Оба ее операнда точно соответствуют параметрам.

15.12.3. Неоднозначность

Наличие в одном и том же классе конвертеров, выполняющих неявные преобразования во встроенные типы, и перегруженных операторов может приводить к неоднозначности при выборе между ними. Например, есть следующее определение класса String с функцией

class String { // ...

public:

String( const char * = 0 );

bool operator== ( const String & ) const; // нет оператора operator== ( const char * )

сравнения:

};

и такое использование оператора operator==:

С++ для начинающих

774

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 * )

class String { // ...

public:

String( const char * = 0 );

bool operator== ( const String & ) const; operator const char*(); // новый конвертер

Если добавить в определение класса String конвертер в тип 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;

С++ для начинающих

775

требует применения определенного пользователем преобразования правого операнда 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;

устоявшей функции.

}