Анимация
JavaScript
|
Главная Библионтека 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 Twice( 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 <complех> class calс { public: double Twice С double d ); private: unsigned Twice( unsigned i ); std::complex<f1oat> TwiceC std::complex<f1oat> с ); i nt mai n() { Calс с; return с.TwiceC 21 ); Ошибка - вызов Twice неоднозначен В этом случае мы не сможем пройти второй шаг. Разрешение перегрузки не может найти наилучшее соответствие в списке кандидатов, поскольку аргумент может быть преобразован как в unsigned, так и в double, и, в соответствии с правилами языка, эти два преобразования одинаково хороши. Поскольку эти две функции имеют одинаковую степень соответствия, компилятор не в состоянии выбрать одну из них и сообщает о неоднозначности вызова. В результате в этом случае компилятор так и не доберется до проверки доступности. Пожалуй, еще интереснее ситуация, когда невозможность соответствия играет большую роль, чем доступность. Рассмотрим еще один вариант примера 16-3. Пример 16-4(6): сокрытие имени глобальной функции #include <string> int TwiceC int i ); Глобальная функция class calс { private: std::string Twice( std::string s ); public: int TestO { return Twice( 21 ); Ошибка, Twice(string) не подходит i nt mainO { return CalcO -Test() ; Здесь мы также не можем пройти второй шаг: разрешение перегрузки ис сможет найти ни одной соответствующей функции в списке кандидатов (в котором теперь находится единственная функция Calc:; Twi се (string)), поскольку аргумент типа int не может быть преобразован в stri ng. Компилятор просто не добсрегся до проверки доступности. Запомните, как только найдена область действия, в которой имеется хотя бы один объект с заданным именем, поиск завершается, даже если канди-дат(ы) оказываются не соответствующими типам аргументов и не могут быть вызваны и/или оказываются недоступны. Поиск других кандидатов в охватывающей области действия проводиться не будет*. Итак, мы получили еще одну часть ответа на вопрос о закрытых членах. • Закрытый член видим всему коду, которому видно определение класса. Это означает, что он участвует в поиске имен и разрешении перегрузки и, таким образом, может сделать вызов некорректным или неоднозначным несмотря на то, что сам он, в любом случае, не может быть вызван. Бьярн Страуструп так писал об этом в своей книге [Stroustrup94]: "Если бы спецификации public/private в первую очередь управляли не доступностью, а видимостью, изменение доступности члена с public на private могло бы привести к незаметному изменению смысла программы - от одной корректной интерпретации [в нашем примере вызов Calc:: Twi се (int)] к другой [в нашем примере вызов Calc: : Twi се (double)]. Это не решающий аргумент, но принятое решение позволяет программистам в процессе отладки добавлять и удалять спецификаторы public и private, не опасаясь, что программа незаметно изменит свой смысл. Я до сих пор гадаю, не явилось ли это решение неким озарением ". И снова доступность Я уже говорил о том, что закрытое имя члена доступно (может использоваться) только для других членов и друзей. Обратите внимание, что я преднамеренно не сказал "может быть вызван только другими членами или друзьями", поскольку на самом деле это не так. Доступность определяет, вправе ли код использовать имя. Позвольте мне подчеркнуть .это цитатой из стандарта. Член класса может быть • закрытым (private); т.е. его имя может использоваться только членами и друзьями класса, в котором он объявлен. Если код, который имеет право непосредственного использования имени (в данном случае член класса или друг) использует эго имя для создания указателя на функцию, а затем передает его другому коду, то получивший его код может воспользоваться этим указателем независимо от того, имеет ли он право на использование имени - имя ему больше не нужно, так как у него есть указатель. Пример 16-5 иллюстрирует применение этого способа на практике - функция-член, имеющая доступ к имени Twi се (int), использует этот доступ для передачи указателя на данный член. пример 16-5: передача доступа class Calc; typedef int (calc::*РметЬег)(int) ; с I ass Calc { public: PMember coughltup() { return &Calc::Twice; } Поиск прекращается даже в тех случаях, когда найденное во время поиска имя не является именем функции. - Прим. ред. 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 [ 30 ] 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |