Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
85
Добавлен:
30.05.2015
Размер:
5.92 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 ); // можно ли вызывать статическую функцию-член?

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