|
Программирование >> Обобщенные обратные вызовы
указатель на функцию), поскольку имя функции не может быть использовано никаким образом, включая получение адреса функции: пример 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 {
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |