Выбрать главу

Но можно же создать объект класса String из C-строки с помощью конструктора класса. Почему компилятор не выполнит неявно такое преобразование:

if ( String( "tulip" ) == flower ) //правильно: вызывается оператор-член

Причина в его неэффективности. Перегруженные операторы не требуют, чтобы оба операнда имели один и тот же тип. К примеру, в классе Text определяются следующие операторы равенства:

class Text {

public:

Text( const char * = 0 );

Text( const Text & );

// набор перегруженных операторов равенства

bool operator==( const char * ) const;

bool operator==( const String & ) const;

bool operator==( const Text & ) const;

// ...

};

и выражение в main() можно переписать так:

if ( Text( "tulip" ) == flower ) // вызывается Text::operator==()

Следовательно, чтобы найти подходящий для сравнения оператор равенства, компилятору придется просмотреть все определения классов в поисках конструктора, способного привести левый операнд к некоторому типу класса. Затем для каждого из таких типов нужно проверить все ассоциированные с ним перегруженные операторы равенства, чтобы понять, может ли хоть один из них выполнить сравнение. А после этого компилятор должен решить, какая из найденных комбинаций конструктора и оператора равенства (если таковые нашлись) лучше всего соответствует операнду в правой части! Если потребовать от компилятора выполнения всех этих действий, то время трансляции программ C++ резко возрастет. Вместо этого компилятор просматривает только перегруженные операторы, определенные как члены класса левого операнда (и его базовых классов, как мы покажем в главе 19).

Разрешается, однако, определять перегруженные операторы, не являющиеся членами класса. При анализе строки в main(), вызвавшей ошибку компиляции, подобные операторы принимались во внимание. Таким образом, сравнение, в котором C-строка стоит в левой части, можно сделать корректным, если заменить операторы равенства, являющиеся членами класса String, на операторы равенства, объявленные в области видимости пространства имен:

bool operator==( const String &, const String & );

bool operator==( const String &, const char * );

Обратите внимание, что эти глобальные перегруженные операторы имеют на один параметр больше, чем операторы-члены. Если оператор является членом класса, то первым параметром неявно передается указатель this. То есть для операторов-членов выражение

flower == "lily"

переписывается компилятором в виде:

flower.operator==( "lily" )

и на левый операнд flower в определении перегруженного оператора-члена можно сослаться с помощью this. (Указатель this введен в разделе 13.4.) В случае глобального перегруженного оператора параметр, представляющий левый операнд, должен быть задан явно.

Тогда выражение

flower == "lily"

вызывает оператор

bool operator==( const String &, const char * );

Непонятно, какой оператор вызывается для второго случая использования оператора равенства:

"tulip" == flower

Мы ведь не определили такой перегруженный оператор:

bool operator==( const char *, const String & );

Но это необязательно. Когда перегруженный оператор является функцией в пространстве имен, то как для первого, так и для второго его параметра (для левого и правого операндов) рассматриваются возможные преобразования, т.е. компилятор интерпретирует второе использование оператора равенства как

operator==( String("tulip"), flower );

и вызывает для выполнения сравнения следующий перегруженный оператор:

bool operator==( const String &, const String & );

Но тогда зачем мы предоставили второй перегруженный оператор:

bool operator==( const String &, const char * );

Преобразование типа из C-строки в класс String может быть применено и к правому операнду. Функция main() будет компилироваться без ошибок, если просто определить в пространстве имен перегруженный оператор, принимающий два операнда String:

bool operator==( const String &, const String & );

Предоставлять ли только этот оператор или еще два:

bool operator==( const char *, const String & );

bool operator==( const String &, const char * );

зависит от того, насколько велики затраты на преобразование из C-строки в String во время выполнения, то есть от “стоимости” дополнительных вызовов конструктора в программах, пользующихся нашим классом String. Если оператор равенства будет часто использоваться для сравнения C-строк и объектов , то лучше предоставить все три варианта. (Мы вернемся к вопросу эффективности в разделе, посвященном друзьям.