Альтернативная форма оператора new – nothrow



 

Стандарт C++ при неудачной попытке выделения памяти вместо генерирования исключения также позволяет оператору new возвращать значение null . Эта форма использования оператора new особенно полезна при компиляции старых программ с применением современного С++‑компилятора. Это средство также очень полезно при замене вызовов функции malloc() оператором new . (Это обычная практика при переводе С‑кода на язык C++.) Итак, этот формат оператора new выглядит следующим образом.

 

 

Здесь элемент p_var – это указатель на переменную типа тип . Этот nothrow ‑формат оператора new работает подобно оригинальной версии оператора new , которая использовалась несколько лет назад. Поскольку оператор new (nothrow)  возвращает при неудаче значение null , его можно "внедрить" в старый код программы, не прибегая к обработке исключений. Однако в новых программах на C++ все же лучше иметь дело с исключениями.

В следующем примере показано, как используется альтернативный вариант new (nothrow) . Нетрудно догадаться, что перед вами вариация на тему предыдущей программы.

 

 

Здесь при использовании nothrow ‑версии после каждого запроса на выделение памяти необходимо проверять значение указателя, возвращаемого оператором new .

 

Перегрузка операторов new и delete

 

Поскольку new и delete – операторы, их также можно перегружать. Несмотря на то что перегрузку операторов мы рассматривали в главе 13, тема перегрузки операторов new и delete была отложена до знакомства с темой исключений, поскольку правильно перегруженная версия оператора new (та, которая соответствует стандарту C++) должна в случае неудачи генерировать исключение типа bad_alloc . По ряду причин вам имеет смысл создать собственную версию оператора new . Например, создайте процедуры выделения памяти, которые, если область кучи окажется исчерпанной, автоматически начинают использовать дисковый файл в качестве виртуальной памяти. В любом случае реализация перегрузки этих операторов не сложнее перегрузки любых других.

Ниже приводится скелет функций, которые перегружают операторы new и delete .

 

 

Тип size_t специально определен, чтобы обеспечить хранение размера максимально возможной области памяти, которая может быть выделена для объекта. (Тип size_t , по сути, –это целочисленный тип без знака.) Параметр size определяет количество байтов памяти, необходимых для хранения объекта, для которого выделяется память. Другими словами, это объем памяти, который должна выделить ваша версия оператора new . Перегруженная функция new должна возвращать указатель на выделяемую ею память или генерировать исключение типа bad_alloc в случае возникновении ошибки. Помимо этих ограничений, перегруженная функция new может выполнять любые нужные действия. При выделении памяти для объекта с помощью оператора new (его исходной версии или вашей собственной) автоматически вызывается конструктор объекта.

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

Чтобы выделить память для массива объектов, а затем освободить ее, необходимо использовать следующие форматы операторов new и delete .

 

 

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

Операторы new и delete , как правило, перегружаются относительно класса. Ради простоты в следующем примере используется не новая схема распределения памяти, а перегруженные функции new и delete , которые просто вызывают С‑ориентированные функции выделения памяти malloc() и free() . (В своем собственном приложении вы вольны реализовать любой метод выделения памяти.)

Чтобы перегрузить операторы new и delete для конкретного класса, достаточно сделать эти перегруженные операторные функции членами этого класса. В следующем примере программы операторы new и delete перегружаются для класса three_d . Эта перегрузка позволяет выделить память для объектов и массивов объектов, а затем освободить ее.

 

 

При выполнении эта программа генерирует такие результаты.

 

 

Первые три сообщения Создание объекта 0, 0, 0 выданы конструктором класса three_d (который не имеет параметров) при выделении памяти для трехэлементного массива. Как упоминалось выше, при выделении памяти для массива автоматически вызывается конструктор каждого элемента. Сообщение Создание объекта 5, б, 7 выдано конструктором класса three_d (который принимает три аргумента) при выделении памяти для одного объекта. Первые три сообщения Разрушение объекта выданы деструктором в результате удаления трехэлементного массива, поскольку при этом автоматически вызывался деструктор каждого элемента массива. Последнее сообщение Разрушение объекта выдано при удалении одного объекта класса three_d . Важно понимать, что, если операторы new и delete перегружены для конкретного класса, то в результате их использования для данных других типов будут задействованы оригинальные версии операторов new и delete . Это означает, что при добавлении в функцию main() следующей строки будет выполнена стандартная версия оператора new .

 

 

И еще. Операторы new и delete можно перегружать глобально. Для этого достаточно объявить их операторные функции вне классов. В этом случае стандартные версии С++‑операторов new и delete игнорируются вообще, и во всех запросах на выделение памяти используются их перегруженные версии. Безусловно, если вы при этом определите версию операторов new и delete для конкретного класса, то эти "классовые" версии будут применяться при выделении памяти (и ее освобождении) для объектов этого класса. Во всех же остальных случаях будут использоваться глобальные операторные функции.

 

Перегрузка nothrow‑версии оператора new

 

Можно также создать перегруженные nothrow ‑версии операторов new и delete . Для этого используйте такие схемы.

 

 

Тип nothrow_t определяется в заголовке <new> . Параметр типа nothrow_t не используется. В качестве упражнения поэкспериментируйте с nothrow ‑версиями операторов new и delete самостоятельно.

 

Глава 18: С++‑система ввода‑вывода

 

С самого начала книги мы использовали С++‑систему ввода‑вывода, но не давали подробных пояснений по этому поводу. Поскольку С++‑система ввода‑вывода построена на иерархии классов, ее теорию и детали невозможно освоить, не рассмотрев сначала классы, наследование и механизм обработки исключений. Теперь настало время для подробного изучения С++‑средств ввода‑вывода.

В этой главе рассматриваются средства как консольного, так и файлового ввода‑вывода. Необходимо сразу отметить, что С++‑система ввода‑вывода – довольно обширная тема, и здесь описаны лишь самые важные и часто применяемые средства. В частности, вы узнаете, как перегрузить операторы "<<" и ">>" для ввода и вывода объектов созданных вами классов, а также как отформатировать выводимые данные и использовать манипуляторы ввода‑вывода. Завершает главу рассмотрение средств файлового ввода‑вывода.

 

Сравнение старой и новой С++‑систем ввода‑вывода

 

В настоящее время существуют две версии библиотеки объектно‑ориентированного ввода‑вывода, причем обе широко используются программистами: более старая, основанная на оригинальных спецификациях языка C++, и новая, определенная стандартом языка C++. Старая библиотека ввода‑вывода поддерживается за счет заголовочного файла <iostream.h> , а новая – посредством заголовка <iostream> . Новая библиотека ввода‑вывода, по сути, представляет собой обновленную и усовершенствованную версию старой. Основное различие между ними состоит в реализации, а не в том, как их нужно использовать.

С точки зрения программиста, есть два существенных различия между старой и новой С++‑библиотеками ввода‑вывода. Во‑первых, новая библиотека содержит ряд дополнительных средств и определяет несколько новых типов данных. Таким образом, новую библиотеку ввода‑вывода можно считать супермножеством старой. Практически все программы, написанные для старой библиотеки, успешно компилируются при использовании новой, не требуя внесения каких‑либо значительных изменений. Во‑вторых, старая библиотека ввода‑вывода была определена в глобальном пространстве имен, а новая использует пространство имен std . (Вспомните, что пространство имен std используется всеми библиотеками стандарта C++.) Поскольку старая библиотека ввода‑вывода уже устарела, в этой книге описывается только новая, но большая часть информации применима и к старой.

 

Потоки C++

 

Потокэто последовательный логический интерфейс, который связан с физическим файлом.

Принципиальным для понимания С++‑системы ввода‑вывода является то, что она опирается на понятие потока. Поток (stream ) – это общий логический интерфейс с различными устройствами, составляющими компьютер. Поток либо синтезирует информацию, либо потребляет ее и связывается с любым физическим устройством с помощью С++‑системы ввода‑вывода. Характер поведения всех потоков одинаков, несмотря на различные физические устройства, с которыми они связываются. Поскольку потоки действуют одинаково, то практически ко всем типам устройств можно применить одни и те же функции и операторы ввода‑вывода. Например, методы, используемые для записи данных на экран, также можно использовать для вывода их на принтер или для записи в дисковый файл.

В самой общей форме поток можно назвать логическим интерфейсом с файлом. С++‑определение термина "файл" можно отнести к дисковому файлу , экрану , клавиатуре , порту , файлу на магнитной ленте и пр. Хотя файлы отличаются по форме и возможностям, все потоки одинаковы. Достоинство этого подхода (с точки зрения программиста) состоит в том, что одно устройство компьютера может "выглядеть" подобно любому другому. Это значит, что поток обеспечивает интерфейс, согласующийся со всеми устройствами.

Поток связывается с файлом при выполнении операции открытия файла, а отсоединяется от него с помощью операции закрытия.

Существует два типа потоков: текстовый и двоичный . Текстовый поток используется для ввода‑вывода символов. При этом могут происходить некоторые преобразования символов. Например, при выводе символ новой строки может быть преобразован в последовательность символов: возврата каретки и перехода на новую строку. Поэтому может не быть взаимно‑однозначного соответствия между тем, что посылается в поток, и тем, что в действительности записывается в файл. Двоичный поток можно использовать с данными любого типа, причем в этом случае никакого преобразования символов не выполняется, и между тем, что посылается в поток, и тем, что потом реально содержится в файле, существует взаимно‑однозначное соответствие.

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

Говоря о потоках, необходимо понимать, что вкладывается в понятие "текущей позиции". Текущая позиция – это место в файле, с которого будет выполняться следующая операция доступа к файлу. Например, если длина файла равна 100 байт, и известно, что уже прочитана половина этого файла, то следующая операция чтения произойдет на байте 50, который в данном случае и является текущей позицией.

Итак, в языке C++ механизм ввода‑вывода функционирует с использованием логического интерфейса, именуемого потоком. Все потоки имеют аналогичные свойства, которые позволяют выполнять одинаковые функции ввода‑вывода, независимо от того, с файлом какого типа существует связь. Под файлом понимается реальное физическое устройство, которое содержит данные. Если файлы различаются между собой, то потоки – нет. (Конечно, некоторые устройства могут не поддерживать все операции ввода‑вывода, например операции с произвольной выборкой, поэтому и связанные с ними потоки тоже не будут поддерживать эти операции.)

 

Встроенные С++‑потоки

 

В C++ содержится ряд встроенных потоков (cin , cout , cerr и clog ), которые автоматически открываются, как только программа начинает выполняться. Как вы знаете, cin – это стандартный входной, а cout – стандартный выходной поток. Потоки cerr и clog (они предназначены для вывода информации об ошибках) также связаны со стандартным выводом данных. Разница между ними состоит в том, что поток clog буферизирован, а поток cerr – нет. Это означает, что любые выходные данные, посланные в поток cerr , будут немедленно выведены, а при использовании потока clog данные сначала записываются в буфер, и реальный их вывод происходит только тогда, когда буфер полностью заполняется. Обычно потоки cerr и clog используются для записи информации об отладке или ошибках.

В C++ также предусмотрены двухбайтовые (16‑битовые) символьные версии стандартных потоков, именуемые wcin , wcout , wcerr и wclog . Они предназначены для поддержки таких языков, как китайский, для представления которых требуются большие символьные наборы. В этой книге двухбайтовые стандартные потоки не используются.

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

 

Классы потоков

 

Как вы узнали в главе 2, С++‑система ввода‑вывода использует заголовок <iostream> , в котором для поддержки операций ввода‑вывода определена довольно сложная иерархия классов. Эта иерархия начинается с системы шаблонных классов. Как отмечалось в главе 16, шаблонный класс определяет форму, не задавая в полном объеме данные, которые он должен обрабатывать. Имея шаблонный класс, можно создавать его конкретные экземпляры. Для библиотеки ввода‑вывода стандарт C++ создает две специализации шаблонных классов: одну для 8‑, а другую для 16‑битовых ("широких") символов. В этой книге описываются классы только для 8‑битовых символов, поскольку они используются гораздо чаще.

С++‑система ввода‑вывода построена на двух связанных, но различных иерархиях шаблонных классов. Первая выведена из класса низкоуровневого ввода‑вывода basic_streambuf . Этот класс поддерживает базовые низкоуровневые операции ввода и вывода и обеспечивает поддержку для всей С++‑системы ввода‑вывода. Если вы не собираетесь заниматься программированием специализированных операций ввода‑вывода, то вам вряд ли придется использовать напрямую класс basic_streambuf . Иерархия классов, с которой С++‑программистам наверняка предстоит работать вплотную, выведена из класса basic_ios . Это – класс высокоуровневого ввода‑вывода, который обеспечивает форматирование, контроль ошибок и предоставляет статусную информацию, связанную с потоками ввода‑вывода. (Класс basic_ios выведен из класса ios_base , который определяет ряд нешаблонных свойств, используемых классом basic_ios .) Класс basic_ios используется в качестве базового для нескольких производных классов, включая классы basic_istream , basic_ostream и basic_iostream . Эти классы используются для создания потоков, предназначенных для ввода данных, вывода и ввода‑вывода соответственно.

Как упоминалось выше, библиотека ввода‑вывода создает две специализированные иерархии шаблонных классов: одну для 8‑, а другую для 16‑битовых символов. Ниже приводится список имен шаблонных классов и соответствующих им "символьных" версий.

 

 

В остальной части этой книги используются имена символьных классов, поскольку именно они применяются в программах. Те же имена используются и старой библиотекой ввода‑вывода. Вот поэтому старая и новая библиотеки совместимы на уровне исходного кода.

И еще: класс ios содержит множество функций‑членов и переменных, которые управляют основными операциями над потоками или отслеживают результаты их выполнения. Поэтому имя класса ios будет употребляться в этой книге довольно часто. И помните: если включить в программу заголовок <iostream> , она будет иметь доступ к этому важному классу.

 

Перегрузка операторов ввода‑вывода

 

В примерах из предыдущих глав при необходимости выполнить операцию ввода или вывода данных, связанных с классом, создавались функции‑члены, назначение которых и состояло лишь в том, чтобы ввести или вывести эти данные. Несмотря на то что в самом этом решении нет ничего неправильного, в C++ предусмотрен более удачный способ выполнения операций ввода‑вывода "классовых" данных: путем перегрузки операторов ввода‑вывода "<<" и ">>".

Оператор "<<" выводит информацию в поток, а оператор ">>" вводит информацию из потока.

В языке C++ оператор "<<" называется оператором вывода или вставки , поскольку он вставляет символы в поток. Аналогично оператор ">>" называется оператором ввода или извлечения , поскольку он извлекает символы из потока.

Как вы знаете, операторы ввода‑вывода уже перегружены (в заголовке <iostream> ), чтобы они могли выполнять операции потокового ввода или вывода данных любых встроенных С++‑типов. Здесь вы узнаете, как определить эти операторы для собственных классов.

 


Дата добавления: 2018-09-22; просмотров: 430; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!