|
Программирование >> Синтаксис инициирования исключений
кто-нибудь вызовет оператор delete для его адреса . Сам по себе объект из памяти не удалится; вы должны явно сообщить своей программе, когда его следует уничтожить. Указатели и ссылки Попытки связать указатели с динамическими объектами часто приводят к недоразумениям. В сущности, они не имеют друг с другом ничего общего. Вы можете получить адрес стекового объекта и выполнить обратное преобразование, то есть разыменование (dereferencing) адреса динамического объекта. И на то, и на другое можно создать ссылку. foo f; foo* p = &f; f.MemberFnC); Использует сам объект p->MemberFn(); Использует его адрес p = new foo; foo& r = *p; Ссылка на объект r.MemberFnC); То же, что и p->MemberFn() Как видите, выбор оператора . или -> зависит от типа переменной и не имеет отношения к атрибутам самого объекта. Раз уж мы заговорили об этом, правильные названия этих операторов (. и ->) - селекторы членов класса (member selectors). Если вы назовете их точкой или стрелкой на семинаре с коктейлями, наступит гробовая тишина, все повернутся и презрительно посмотрят на вас, а в дальнем углу кто-нибудь выронит свой бокал. Недостатки стековых объектов Если использовать оператор delete для стекового объекта, то при большом везении ваша программа просто грохнется. А если вам (как и большинству из нас) не повезет, то программа начнет вести себя, как ревнивая любовница - она будет вытворять, всякие гадости в разных местах памяти, но не скажет, на что же она разозлилась. Дело в том, что в большинстве реализаций C++ оператор new записывает пару скрытых байтов перед возвращаемым адресом. В этих байтах указывается размер выделенного блока. По ним оператор delete определяет, сколько памяти за указанным адресом следует освободить. При выделении памяти под стековые объекты оператор new не вызывается, поэтому эти дополнительные данные отсутствуют. Если вызвать оператор delete для стекового объекта, он возьмет содержимое стека над вашей переменной и интерпретирует его как размер освобождаемого блока. Итак, мы знаем по крайней мере две причины, по которым следует избегать стековых объектов - если у вас нет действительно веских доводов в их пользу: 1. Адрес стекового объекта может быть сохранен и использован после выхода за границы области действия объекта. 2. Адрес стекового объекта может быть передан оператору delete. Следовательно, для стековых объектов действует хорошее правило: Никогда не получайте их адреса или адреса их членов. Достоинства стековых объектов С другой стороны, память в стеке выделяется с головокружительной быстротой - так же быстро, как компилятор выделяет память под другие автоматические переменные (скажем, целые). Оператор new (по крайней мере, его стандартная версия) тратит несколько тактов на то, чтобы решить, откуда взять блок памяти и где оставить данные для его последующего освобождения. Быстродействие - одна из веских причин в пользу выделения памяти из стека. Как вы вскоре убедитесь, существует немало способов ускорить работу оператора new, так что эта причина менее важна, чем может показаться с первого взгляда. Автоматическое удаление - второе большое преимущество стековых объектов, поэтому программисты часто создают маленькие вспомогательные стековые классы, которые играют роль обертки для динамических объектов. В следующем забавном примере динамический класс Foo упаковывается в стековый класс PFoo. Конструктор выделяет память для Foo; деструктор освобождает ее. Если вы незнакомы с операторами преобразования, обратитесь к соответствующему разделу этой главы. В двух словах, функция operator Foo*() позволяет использовать класс PFoo везде, где должен использоваться Foo* - например, при вызове функции g() . class PFoo { private: Foo* f; public: PFoo() : f(new Foo) {} ~PFoo() { delete f; } operator Foo*() { return f; } void g(Foo*); PFoo p; g(p); Вызывает функцию operator Foo*() для преобразования Уничтожается p, а за ним - Foo Обратите внимание, что этот класс не совсем безопасен, поскольку адрес, возвращаемый функцией operator Foo*() , становится недействительным после удаления вмещающего PFoo. Мы разберемся с этим чуть позже. Мы еще не раз встретимся с подобными фокусами. Вся соль заключается в том, что стековые объекты могут пригодиться просто из-за того, что их не приходится удалять вручную. Вскоре я покажу вам, как организовать автоматическое удаление динамических объектов, но эта методика очень сложна и вряд ли пригодна для повседневного применения. У стековых объектов есть еще одно преимущество - если ваш компилятор поддерживает ANSI-совместимую обработку исключений (exception). Когда во время раскрутки стека происходит исключение, деструкторы стековых объектов вызываются автоматически. Для динамических объектов этого не случается, и ваша куча может превратиться в настоящий хаос. Рискуя повториться, я скажу, что мы вернемся к этой теме позднее. Области действия и функции Одно из значительных преимуществ C++ над С - возможность ограничения области действия символических имен. Впрочем, это палка о двух концах, поскольку правила определения области действия иногда довольно запутанны. Кроме того, в C++ появилась перегрузка функций и - как ее расширение - перегрузка операторов. Предполагается, что вы уже знакомы с азами, поэтому в своем кратком обзоре я ограничусь лишь некоторыми нетривиальными особенностями функций и областей действия. Области действия Область действия создается следующими конструкциями: класс; структура; объединение; блок; глобальное пространство имен. Символические имена, объявленные в области действия, относятся только к данной области. Они не ограничиваются перечислениями и простыми переменными. Структуры, классы и функции также могут определяться в конкретной области действия. Классы Класс в C++ - нечто большее, чем простая структура данных. Это аналог модуля из других языков программирования, средство упорядочения символьных имен. class Foo { public: static int y; Глобальная переменная static void GFn(); Глобальная функция int x; Переменная класса Foo(); Конструктор void Fn(); Функция класса typedef int (*IntFn)(); Тип enum Status { kOpen = 0, kClosed }; Другой тип struct Bar { Вложенная структура int a; int b; static void BarFn(); private: void Hn(); В этом фрагменте приведены некоторые вариации на тему классов. Переменная у - глобальная переменная, a GFn() - глобальная функция, хотя область действия их имен ограничивается классом Foo. Во всех функциях класса Foo к ним можно обращаться просто по имени, но за его пределами необходимо использовать оператор области действия :: : Foo::Foo() GFn(); Мы уже находимся в области действия Foo void f() Foo::GFn(); Необходимо задать область действия Аналогично, определение типа IntFn, перечисление Status и даже вложенную структуру Bar также можно использовать без указания области действия в функциях класса Foo, но в любом другом месте эту область необходимо задать. Для вложенных типов с открытой видимостью синтаксис указания области действия может принять несколько устрашающий вид, как видно из следующего примера для структуры Ваr: Foo::Bar b; Foo::Bar::BarFn(); По этой причине вложенные структуры либо делаются тривиальными, либо доступ к ним ограничивается. Члены класса х, Foo и Fn(), имеют смысл лишь в контексте конкретного экземпляра (instance) этого класса. Для обращения к ним используются операторы-селекторы членов класса, . и Широкие массы (и, как я выяснил на собственном горьком опыте, даже разработчики компиляторов C++) почти не знают о том, что с помощью селекторов можно вызывать статические функции класса и обращаться
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки. |