Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование на C / C++ / Ален И. Голуб. Правила программирования на Си и Си++ [pdf]

.pdf
Скачиваний:
237
Добавлен:
02.05.2014
Размер:
5.67 Mб
Скачать

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

762

15.11. Разрешение перегрузки и функции-члены A

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

разрешение очень похоже на аналогичную процедуру для обычных функций и состоит из тех же трех шагов:

1.Отбор функций-кандидатов.

2.Отбор устоявших функций.

3.Выбор наилучшей из устоявших функции.

Однако есть небольшие различия в алгоритмах формирования множества кандидатов и отбора устоявших функций-членов. Эти различия мы и рассмотрим в настоящем разделе.

15.11.1. Объявления перегруженных функций-членов

class myClass { public:

void f( double );

char f( char, char ); // перегружает myClass::f( double ) // ...

Функции-члены класса можно перегружать:

};

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

class myClass { public:

void mf();

double mf(); // ошибка: так перегружать нельзя

// ...

компиляции:

};

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

class myClass { public:

void mf();

void mf(); // ошибка: повторное объявление

// ...

повторное объявление:

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

763

};

Все функции из множества перегруженных должны быть объявлены в одной и той же области видимости. Поэтому функции-члены никогда не перегружают функций, объявленных в пространстве имен. Кроме того, поскольку у каждого класса своя область видимости, функции, являющиеся членами разных классов, не перегружают друг друга.

Множество перегруженных функций-членов может содержать как статические, так и

class myClass { public:

void mcf( double );

static void mcf( int* ); // перегружает myClass::mcf( double ) // ...

нестатические функции:

};

Какая из функций-членов будет вызвана статическая или нестатическая зависит от результатов разрешения перегрузки. Процесс разрешения в ситуации, когда устояли как статические, так и нестатические члены, мы подробно рассмотрим в следующем разделе.

15.11.2. Функции-кандидаты

mc.mf( arg );

Рассмотрим два вида вызовов функции-члена: pmc->mf( arg );

где mc выражение типа myClass, а pmc выражение типа указатель на тип myClass”. Множество кандидатов для обоих вызовов составлено из функций, найденных в области видимости класса myClass при поиске объявления mf().

Аналогично для вызова функции вида

myClass::mf( arg );

множество кандидатов также состоит из функций, найденных в области видимости класса myClass при поиске объявления mf(). Например:

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

764

class myClass { public:

void mf( double );

void mf( char, char = '\n' ); static void mf( int* );

// ...

};

int main() { myClass mc; int iobj; mc.mf( iobj );

}

Кандидатами для вызова функции в main() являются все три функции-члена mf(),

void mf( double );

void mf( char, char = '\n' );

объявленные в myClass: static void mf( int* );

Если бы в myClass не было объявлено ни одной функции-члена с именем mf(), то множество кандидатов оказалось бы пустым. (На самом деле рассматривались бы также и функции из базовых классов. О том, как они попадают в это множество, мы поговорим в разделе 19.3.) Если для вызова функции не оказывается кандидатов, компилятор выдает сообщение об ошибке.

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

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

class myClass { public:

void mf( double );

void mf( char, char = '\n' ); static void mf( int* );

// ...

};

int main() { myClass mc; int iobj;

mc.mf( iobj ); // какая именно функция-член mf()? Неоднозначно

Например:

}

В этом фрагменте для вызова mf() из main() есть две устоявшие функции:

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

765

void mf( double );

void mf( char, char = '\n' );

mf(double) устояла потому, что у нее только один параметр и существует стандартное преобразование аргумента iobj типа int в параметр типа double;

mf(char,char) устояла потому, что для второго параметра имеется значение

по умолчанию и существует стандартное преобразование аргумента iobj типа int в тип char первого формального параметра.

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

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

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

class myClass { public:

static void mf( int ); char mf( char );

};

int main() { char cobj;

myClass::mf( cobj ); // какая именно функция-член?

статические, так и нестатические члены:

}

Здесь функция-член mf() вызывается с указанием имени класса и оператора разрешения области видимости myClass::mf(). Однако не задан ни объект (с оператором точка”), ни указатель на объект (с оператором стрелка”). Несмотря на это, нестатическая функция-член mf(char) все же включается в множество устоявших наряду со статическим членом mf(int).

Затем процесс разрешения перегрузки продолжается: на основе ранжирования преобразований типов, примененных к фактическим аргументам, чтобы выбрать наилучшую из устоявших функций. Аргумент cobj типа char точно соответствует

формальному параметру mf(char) и может быть расширен до типа формального параметра mf(int). Поскольку ранг точного соответствия выше, то выбирается функция mf(char).

Однако эта функция-член не является статической и, следовательно, вызывается только

через объект или указатель на объект класса myClass с помощью одного из операторов доступа. В такой ситуации, если объект не указан и, значит, вызов функции невозможен (как раз наш случай), компилятор считает его ошибкой.

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

766

Еще одна особенность функций-членов, которую надо принимать во внимание при формировании множества устоявших функций, – это наличие спецификаторов const или volatile у нестатических членов. (Они рассматривались в разделе 13.3.) Как они влияют на процесс разрешения перегрузки? Пусть в классе myClass есть следующие

class myClass { public:

static void mf( int* ); void mf( double ); void mf( int ) const; // ...

функции-члены:

};

Тогда и статическая функция-член mf(int*), и константная функция mf(int), и

неконстантная функция mf(double) включаются в множество кандидатов для

int main() {

const myClass mc; double dobj;

mc.mf( dobj ); // какая из функций-членов mf()?

показанного ниже вызова. Но какие из них войдут в множество устоявших?

}

Исследуя преобразования, которые надо применить к фактическим аргументам, мы обнаруживаем, что устояли функции mf(double) и mf(int). Тип double фактического

аргумента dobj точно соответствует типу формального параметра mf(double) и может быть приведен к типу параметра mf(int) с помощью стандартного преобразования.

Если при вызове функции-члена используются операторы доступа точкаили стрелка”,

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

mc это константный объект, для которого можно вызывать только нестатические константные функции-члены. Следовательно, неконстантная функция-член mf(double) исключается из множества устоявших, и остается в нем единственная функция mf(int), которая и вызывается.

А если константный объект использован для вызова статической функции-члена? Ведь для такой функции нельзя задавать спецификатор const или volatile, так можно ли ее

class myClass { public:

static void mf( int ); char mf( char );

};

int main() {

const myClass mc; int iobj;

mc.mf( iobj ); // можно ли вызывать статическую функцию-член?

вызывать через константный объект?

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

767

}

Статические функции-члены являются общими для всех объектов одного класса. Напрямую они могут обращаться только к статическим членам класса. Так, нестатические члены константного объекта mc недоступны статической mf(int). По этой причине разрешается вызывать статическую функцию-член для константного объекта с помощью операторов точкаили стрелка”.

Таким образом, статические функции-члены не исключаются из множества устоявших и при наличии спецификаторов const или volatile у объекта, для которого они вызваны. Статические функции-члены рассматриваются как соответствующие любому объекту или указателю на объект своего класса.

В примере выше mc константный объект, поэтому функция-член mf(char) исключается из множества устоявших. Но функция-член mf(int) в нем остается, так как является статической. Поскольку это единственная устоявшая функция, она и оказывается наилучшей.

15.12. Разрешение перегрузки и операторы A

В классах могут быть объявлены перегруженные операторы и конвертеры. Предположим,

SomeClass sc;

при инициализации встретился оператор сложения: int iobj = sc + 3;

Как компилятор решает, что следует сделать: вызвать перегруженный оператор для класса SomeClass или конвертировать операнд sc во встроенный тип, а затем уже воспользоваться встроенным оператором?

Ответ зависит от множества перегруженных операторов и конвертеров, определенных в SomeClass. При выборе оператора для выполнения сложения применяется процесс разрешения перегрузки функции. В данном разделе мы расскажем, как этот процесс позволяет выбрать нужный оператор, когда операндами являются объекты типа класса.

При разрешении перегрузки используется все та же процедура из трех шагов, представленная в разделе 9.2:

1.Отбор функций-кандидатов.

2.Отбор устоявших функций.

3.Выбор наилучшей из устоявших функции.

Рассмотрим эти шаги более детально.

Разрешение перегрузки функции не применяется, если все операнды имеют встроенные типы. В таком случае гарантированно употребляется встроенный оператор. (Использование операторов с операндами встроенных типов описано в главе 4.) Например:

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

768

class SmallInt { public:

SmallInt( int );

};

SmallInt operator+ ( const SmallInt &, const SmallInt & ); void func() {

int i1, i2;

int i3 = i1 + i2;

}

Поскольку операнды i1 и i2 имеют тип int, а не тип класса, то при сложении используется встроенный оператор +. Перегруженный operator+(const SmallInt &, const SmallInt &) игнорируется, хотя операнды можно привести к типу SmallInt с

помощью определенного пользователем преобразования в виде конструктора SmallInt(int). Описанный ниже процесс разрешения перегрузки в таких ситуациях не применяется.

Кроме того, разрешение перегрузки для операторов употребляется только в случае

void func() { SmallInt si(98); int iobj = 65;

int res = si + iobj; // использован операторный синтаксис

использования операторного синтаксиса:

}

Если вместо этого использовать синтаксис вызова функции:

int res = operator+( si, iobj ); // синтаксис вызова функции

то применяется процедура разрешения перегрузки для функций в пространстве имен (см.

// синтаксис вызова функции-члена

раздел 15.10). Если же использован синтаксис вызова функции-члена: int res = si.operator+( iobj );

то работает соответствующая процедура для функций-членов (см. раздел 15.11).

15.12.1. Операторные функции-кандидаты

Операторная функция является кандидатом, если она имеет то же имя, что и вызванная.

SmallInt si(98); int iobj = 65;

При использовании следующего оператора сложения int res = si + iobj;

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

769

операторной функцией-кандидатом является operator+. Какие объявления operator+ принимаются во внимание?

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

∙ множество операторов, видимых в точке вызова. Объявления функции operator+(), видимые в точке использования оператора, являются кандидатами. Например, operator+(), объявленный в глобальной области видимости, –

SmallInt operator+ ( const SmallInt &, const SmallInt & );

int main() { SmallInt si(98); int iobj = 65;

int res = si + iobj; // ::operator+() - функция-кандидат

кандидат в случае применения operator+() внутри main():

}

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

namespace NS {

class SmallInt { /* ... */ };

SmallInt operator+ ( const SmallInt&, double );

}

int main() {

//si имеет тип SmallInt:

//этот класс объявлен в пространстве имен NS NS::SmallInt si(15);

//NS::operator+() - функция-кандидат

int res = si + 566; return 0;

считаются кандидатами:

}

Операнд si имеет тип класса SmallInt, объявленного в пространстве имен NS. Поэтому перегруженный operator+(const SmallInt, double), объявленный в том же пространстве, добавляется к множеству кандидатов;

∙ множество операторов, объявленных друзьями классов, к которым принадлежат операнды. Если операнд принадлежит к типу класса и в определении этого класса есть одноименные применяемому оператору функции-друзья, то они добавляются к множеству кандидатов:

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

770

namespace NS { class SmallInt {

friend SmallInt operator+( const SmallInt&, int ) { /* ... */ }

};

}

int main() { NS::SmallInt si(15);

// функция-друг operator+() - кандидат int res = si + 566;

return 0;

}

Операнд si имеет тип SmallInt. Операторная функция operator+(const SmallInt&, int), являющаяся другом этого класса, – член пространства имен NS, хотя непосредственно в этом пространстве она не объявлена. При обычном поиске в NS эта операторная функция не будет найдена. Однако при использовании operator+() с аргументом типа SmallInt функции-друзья, объявленные в области видимости этого класса, включаются в рассмотрение и добавляются к множеству кандидатов.

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

∙ множество операторов-членов, объявленных в классе левого операнда. Если такой операнд оператора operator+() имеет тип класса, то в множество функций- кандидатов включаются объявления operator+(), являющиеся членами этого

class myFloat { myFloat( double );

};

class SmallInt { public:

SmallInt( int );

SmallInt operator+ ( const myFloat & );

};

int main() { SmallInt si(15);

int res = si + 5.66; // оператор-член operator+() - кандидат

класса:

}

Оператор-член SmallInt::operator+(const myFloat &), определенный в SmallInt,

включается в множество функций-кандидатов для разрешения вызова operator+() в main();

∙ множество встроенных операторов. Учитывая типы, которые можно использовать со встроенным operator+(), кандидатами являются также:

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

771

int operator+( int, int );

double operator+( double, double ); T* operator+( T*, I );

T* operator+( I, T* );

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

Любое из первых четырех множеств может оказаться пустым. Например, если среди членов класса SmallInt нет функции с именем operator+(), то четвертое множество будет пусто.

Все множество операторных функций-кандидатов является объединением пяти

namespace NS { class myFloat {

myFloat( double );

};

class SmallInt {

friend SmallInt operator+( const SmallInt &, int ) { /* ... */ } public:

SmallInt( int ); operator int();

SmallInt operator+ ( const myFloat & ); // ...

};

SmallInt operator+ ( const SmallInt &, double );

}

int main() {

//тип si - class SmallInt:

//Этот класс объявлен в пространстве имен NS NS::SmallInt si(15);

int res = si + 5.66; // какой operator+()? return 0;

подмножеств, описанных выше:

}

В эти пять множеств входят семь операторных функций-кандидатов на роль operator+() в main():

∙ первое множество пусто. В глобальной области видимости, а именно в ней употреблен operator+() в функции main(), нет объявлений перегруженного оператора operator+();

∙ второе множество содержит операторы, объявленные в пространстве имен NS, где определен класс SmallInt. В этом пространстве имеется один оператор:

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