Программирование >>  Обобщенные обратные вызовы 

1 ... 26 27 28 [ 29 ] 30 31 32 ... 84


указатель на функцию), поскольку имя функции не может быть использовано никаким образом, включая получение адреса функции:

пример 16-1: доступ к No::satisfaction невозможен

class NO { private:

virtual void Satisfaction() { }

i nt mai n() { No no;

no.Satisfaction C); ошибка

typedef void (no::*PMember)(); pMember p = too::satisfaction ; ошибка return (no.*p)(); иу-ну...

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

Пример 16-1, продолжение: производный класс может перекрыть закрытый виртуальный член, но не

может к нему обратиться

ass Derived : publiс No { vi rtual void satisfaction() { корректно NO::Sati sfacti on () ; Ошибка

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

Закрытое (private) имя члена доступно только для других членов и друзей.

Если бы на этом все и заканчивалось, это была бы самая короткая (и бессмысленная) задача в книге. Но, как вы понимаете, на доступности имени наш рассказ не заканчивается.

Видимость

Ключевое слово private управляет доступностью членов, но есть еще одна связанная с ней концепция, которую часто путают с доступностью, а именно видимость. Вернемся к коду, приведенному в вопросе задачи: будет ли он компилироваться и корректно выполняться?

Краткий ответ - нет. В приведенном виде программа некорректна и компилироваться не будет по двум причинам. Первая причина - довольно очевидная ошибка.

Пример 16-2 (код из условия задачи)

Twice(x) возвращает 2* х

class calс { public:

Мошеннические способы наподобие подмены ключевого слова pri vate словом publ i с (см. задачу 15) мы не рассматриваем.



double Twice С double d ); private:

int Twice( int i );

std::compl ex<float> Twice( std::complex<float> с );

int mainC) { Calc c;

return c.TwiceC 21 );

Каждый программист на С++ знает, что несмотря на то, что версия Twice, работающая с комплексным объектом, не доступна коду в функции main, она остается видима и создает зависимость на уровне исходного текста. В частности, несмотря на то, что код функции main, вероятно, ничего не знает о типе complex, он не в состоянии использовать имя Twice (compl ex<float>) (ни вызвать .эту функцию, ни получить ее адрес), кроме того, использование complex никоим образом не может повлиять на размер Calc или его размещение в памяти, но все равно для компиляции этого кода тре(5уется, как минимум, предварительное объявление complex. (Если бы функция Twice (compl ex<float>) была определена как встраиваемая, то требовалось бы также полное определение типа complex, несмотря на невозможность вызова этой функции.)

Итак, мы получили следующую часть ответа на вопрос о закрытых членах.

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

Все знают, что исправить первую ошибку очень просто -- добавив #include <complex>. Теперь у нас останется только одна, но менее очевидная проблема.

Пример 16-3: частично исправленный пример 16-2

#i nclude <complex> class Calc { public:

double Twice( double d ); private:

int TwiceC int i );

std::complex<float> Twice( std::complex<float> с );

int mainO { Calc c;

return c.TWiceC 21 ); ошибка, Twice недоступна

Этот результат оказывается неожиданным для большого количества профаммистов на С++. Некоторые профаммисты ожидают, что, поскольку доступна перефузка функции Twice, которая принимает параметр типа double, а число 21 можно привести к этому типу, то именно эта функция должна быть вызвана. На самом деле это не так по очень простой причине: разрешение перефрки выполняется до проверки доступности.

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

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



содержит объект по имени Twice - и даже не один, а три, и все они попадают в список кандидатов. (Дополнительная информация о поиске имен в С++ и о его влиянии на разработку классов и их интерфейсов есть в [SutterOO].)

2. Разрешение перегрузки. Затем компилятор выполняет разрешение перегрузки для того, чтобы выбрать единственного кагздидата из списка, наилучшим образом соответствующего типам аргументов вызова. В нашем случае передаваемый функции аргумент - 21, тип которого i nt, а функции из списка кандидатов принимают параметры типов double, int и complex<float>. Очевидно, что реально передаваемый параметр наилучшим образом соответствует аргументу типа int (точное соответствие, не требующее преобразования типов), так что для вызова выбирается функция Twi ce(i nt).

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

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

Интересно, что неоднозначное соответствие типам параметров играет большую роль, чем доступность. Рассмотрим небольшое изменение примера 16-3.

пример 1б-4(а): внесение неоднозначности

#include <complex> class calс { public:

double Twice( double d );

private:

unsigned Twice( unsigned i );

std: : complex<f 1 oat> Twi ce( std: : cotnplex<f 1 oat> с ) ;

i nt mai n() {

Calс с;

return c.TwiceC 21 ); Ошибка - вызов Twice

неоднозначен

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

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

Пример 16-4(6): сокрытие имени глобальной функции

#include <string>

int Twice( int i ); Глобальная функция

class calс { private:

std::string Twice( std::string s ); public:

int TestO {

return twice( 21 ); Ошибка, Twice(string) не

подходит

i nt mainO {



1 ... 26 27 28 [ 29 ] 30 31 32 ... 84

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика