|
Программирование >> Обобщенные обратные вызовы
выбора наилучшего соответствия среди найденных имен; эго означает, что остальные охватывающие области видимости (в данном случае - глобальная область видимости) более не рассматриваются и все функции в них оказываются скрыты. Далее компилятор просматривает все обнаруженные имена, выбирает функцию при помощи разрешения перегрузки и проверяет права доступа к ней, чтобы выяснить, может ли найденная функция быть вызвана в данном контексте. Внешние области видимости игнорируются, даже если ни одна из найденных перегруженных функций не имеет подходящей сигнатуры, т.е. ни одна из них не может быть вызвана. Внешние области видимости также игнорируются, если функция с соответствующей вызову сигнатурой не может быть вызвана из-за исдосгупности. (Более детально вопросы поиска и сокрытия имен рассмотрены в [SutterOO].) Это означает, что если класс (или любой из его базовых классов) содержит собственный оператор new с любой сигнатурой, то эта функция скрывает все глобальные операгоры new и вы не имеете возможности записать обычное выражение с оператором new. в котором будет использоваться глобальная версия оператора. Вот что это обозначает в контексте примера 22-3. Derived* р1 = new Derived; Ошибка: соответствия нет Derived* р2 = newCstd:rnothrow) Derived; Ошибка: соответствия нет void* рЗ = /* некоторая область памяти, достаточная для размещения Derived */ ; new СрЗ) Derived; ошибка: соответствия нет FastMemory f; Derived* р4 = new(f) Derived; вызов Base::operator new() Ho что если мы хотим использовать глобальные версии операторов, как минимум в первых двух случаях, там, где перегрузка в базовом классе приводит к ошибке? Единственный разумный способ обеспечить возможность классу Derived использовать глобальные операторы new - это предоставить соответствующие функции в классе, которые просто передают вызов глобальным операторам (в противном случае в коде надо использовать квалифицированные имена :: new, чтобы компилятор использовал глобальные версии операторов). Это приводит к интересному выводу, который лучше всего выразить в виде рекомендации (см. также [Meyers97]). > Рекомендация Если вы предоставляете хотя бы один специфичный для данного класса оператор new, следует также обеспечить класс обычным (без дополнительных параметров) оператором new. Специфичная для класса версия оператора почти всегда должна сохранять семантику глобальной версии, поэтому следует объявлять ее со спецификацией исключений throw(std::badalToc), а рсализовывать се желательно с использованием глобальной версии - если только вам действительно не нужны некоторые специфические действия оператора new. предпочтительная реализация оператора new, специфичного для класса void* C::operator newCstd: :size .t s) throw(std: :bad a!loc) { return ::operator new( s ); Заметим, что вы можете вызвать замещенную стандартную версию оператора new вместо используемой по умолчанию, но обычно это именно то, что требуется: в большинстве случаев замещенный глобальный оператор new используется для отладки или контроля за использованием памяти, и такое поведение желательно отразить и в специфичных для классов версиях new. Если же вы не хотите поступать таким образом, то ваш класс будет невозможно использовать кодом, который будет пытаться динамически распределять память для объектов обычным способом. > Рекомендация Если вы предоставляете хотя бы один специфичный для данного класса оператор new, следует также обеспечить класс размещающим оператором new. Этот оператор должен сохранять семантику глобальной версии, поэтому его следует объявлять как не генерирующий исключений и реализовать посредством глобальной версии. предпочтительная реализация размещающего оператора new, специфичного для класса void* С;:operator new( std::size t s, void* p ) throwC) { return ::operator new( s, p ); Если вы этого не сделаете, то не сможете работать с кодом, который использует размещающий оператор new с вашим классом. В частности, реализации контейнеров стандартной библиотеки часто используют размещающие конструирование объектов и ожидают, что оно будет работать, как обычно; в конце концов, это единственный способ явного вызова конструктора в С++. Если вы не напишете такой оператор new, то, скорее всего, вы не сможете воспользоваться даже std: :vector<C>. > Рекомендация Если вы предоставляете хотя бы один специфичный для данного класса оператор new, то следует также предоставить оператор new, не генерирующий исключений, на тот случай, если пользователи вашего класса захотят им воспользоваться; в противном случае этот оператор окажется скрытым другими перефузками оператора new в данном классе. Реализовать этот оператор можно с использованием глобального не генерирующего исключений new. вариант А реализации не генерирующего исключений оператора new класса void* C::operator new(std::size t s, const std::nothrow t& n) throwO { return ::operator new( s, n ); } Другой вариант реализации использует обычный оператор new класса (выполняется то же, что глобальным не генерирующим исключений оператором new; однако при этом мы застрахованы от изменения глобального оператора другим программистом): вариант Б реализации не генерирующего исключений оператора new класса void* С::operator new(std: :size t s, const std: :nothrow..t&) throwO try { return С::operator new( s ); catchC ... ) { return 0; Заметим, что рассмотршные тартат тзово* тую5алънъ\х операторов иеволиож-но реализовать при помощи using-объявления, такого как using .-.operator Единственное место, где такое объявление может оказаться полезным - в описании класса с. В пределах класса можно использовать только using-объявления, которые вносят имена из базовых классов, но не такие, как имена из глобальной области видимости или имена из других классов. Требование, чтобы вызывающий код сам добавлял using-объяштения, не только обременительно, но и бесполезно, поскольку мы можем быть не в состоянии его изменять. Часть кода может находиться в модулях, доступных только для чтения, как, например, библиотеки сторонних производителей, или даже внутри стандартной библиотеки С++, если мы попытаемся обеспечить стандартные контейнеры доступом к специфичному для класса размещающему оператору new. Резюме Если вы предоставляете хотя бы один специфичный для данного класса оператор new, то: следует также обеспечить класс обычным (без дополнительных параметров) оператором new; следует также обеспечить класс размещающим оператором new; следует также обеспечить класс оператором new, не генерирующим исключений, на тот случай, если пользователи вашего класса захотят им воспользоваться; в противном случае этот оператор окажется скрытым другими перегрузками оператора new в данном классе. В следующей задаче мы более подробно разберемся, что же означает сбой оператора new и как лучше всего обнаружить и обработать его. Мы также увидим, что в своих программах желательно избегать применения new(nothrow), но что еще более неожиданно, мы обнаружим, что у ряда популярных платформ отказы при распределении памяти не сообщают о себе так, как того требует стандарт.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |