|
Программирование >> Арифметические и логические операции
А что если я хочу, чтобы локальная переменная умерла раньше закрывающей фигурной скобки? Могу ли я при крайней необходимости вызвать деструктор для локальной переменной? Нет! Предположим, что (желаемый) побочный эффект от вызова деструктора для локального объекта File заключается в закрытии файла. И предположим, что у нас есть экземпляр f класса File и мы хотим, чтобы файл f был закрыт раньше конца своей области видимости (т.е., раньше }): void someCode() File f; [Этот код выполняется при открытом f] <-- Нам нужен эффект деструктора f здесь [Этот код выполняется после закрытия f] Для этой проблемы есть простое решение. Но пока запомните только следующее: нельзя явно вызывать деструктор. Хорошо, я не буду явно вызывать деструктор. Но как мне справиться с этой проблемой? Просто поместите вашу локальную переменную в отдельный блок {...}, соответствующий необходимому времени жизни этой переменной: File f; [В этом месте f еще открыт] деструктор f будет автоматически вызван здесь! [В этом месте f уже будет закрыт] А что делать, если я не могу поместить переменную в отдельный блок? В большинстве случаев вы можете воспользоваться дополнительным блоком {...} для ограничения времени жизни вашей переменной. Но если по какой-то причине вы не можете добавить блок, добавьте функцию-член, которая будет выполнять те же действия, что и деструктор. Но помните: вы не можете сами вызывать деструктор! Например, в случае с классом File, вы можете добавить метод close(). Обычный деструктор будет вызывать close(). Обратите внимание, что метод close() должен будет как-то отмечать объект File, с тем чтобы последующие вызовы не пытались закрыть уже закрытый файл. Например, можно устанавливать переменную-член fileHandle в какое-нибудь неиспользуемое значение, типа -1, и проверять вначале, не содержит ли fileHandle значение -1. class File { public: void close(); ~File(); ... private: int fileHandle ; fileHandle >= 0 если/только если файл открыт File::~File() close(); void File::close() if (fileHandle >= 0) { [Вызвать системную функцию для закрытия файла] fileHandle = -1; Обратите внимание, что другим методам класса File тоже может понадобиться проверять, не установлен ли fileHandle в -1 (т.е., не закрыт ли файл). Также обратите внимание, что все конструкторы, которые не открывают файл, должны устанавливать fileHandle в -1. А могу ли я явно вызывать деструктор для объекта, созданного при помощи new? Скорее всего, нет. За исключением того случая, когда вы использовали синтаксис размещения для оператора new, вам следует просто удалять объекты при помощи delete, а не вызывать явно деструктор. Предположим, что вы создали объект при помощи обычного new: Fred* p = new Fred(); В таком случае деструктор Fred::~Fred() будет автоматически вызван, когда вы удаляете объект: delete p; Вызывает p->~Fred() Вам не следует явно вызывать деструктор, поскольку этим вы не освобождаете память, выделенную для объекта Fred. Помните: delete p делает сразу две вещи: вызывает деструктор и освобождает память. Что такое синтаксис размещения new ( placement new ) и зачем он нужен? Есть много случаев для использования синтаксиса размещения для new. Самое простое - вы можете использовать синтаксис размещения для помещения объекта в определенное место в памяти. Для этого вы указываете место, передавая указатель на него в оператор new: #include <new> Необходимо для использования синтаксиса размещения #include Fred.h Определение класса Fred void someCode() char memory[sizeof(Fred)]; #1 void* place = memory; #2 Fred* f = new(place) Fred(); #3 Указатели f и place будут равны ... В строчке #1 создаётся массив из sizeof(Fred) байт, размер которого достаточен для хранения объекта Fred. В строчке #2 создаётся указатель place, который указывает на первый байт массива (опытные программисты на С наверняка заметят, что можно было и не создавать этот указатель; мы это сделали лишь чтобы код был более понятным). В строчке #3 фактически происходит только вызов конструктора Fred::Fred(). Указатель this в конструкторе Fred будет равен указателю place. Таким образом, возвращаемый указатель тоже будет равен place. Совет: Не используйте синтаксис размещения new, за исключением тех случаев, когда вам действительно нужно, чтобы объект был размещён в определённом месте в памяти. Например, если у вас есть аппаратный таймер, отображённый на определённый участок памяти, то вам может понадобиться поместить объект Clock по этому адресу. Опасно: Используя синтаксис размещения new вы берёте на себя всю ответственность за то, что передаваемый вами указатель указывает на достаточный для хранения объекта участок памяти с тем выравнива- нием (alignment), которое необходимо для вашего объекта. Ни компилятор, ни библиотека не будут проверять корректность ваших действий в этом случае. Если ваш класс Fred должен быть выровнен по четырёхбайтовой границе, но вы передали в new указатель на не выровненный участок памяти, у вас могут быть большие неприятности (если вы не знаете, что такое выравнивание (alignment), пожалуйста, не используйте синтаксис размещения new). Мы вас предупредили. Также на вас ложится вся ответственность по уничтожению размещённого объекта. Для этого вам необходимо явно вызвать деструктор: void someCode() char memory[sizeof(Fred)]; void* p = memory; Fred* f = new(p) Fred(); f->~Fred(); Явный вызов деструктора для размещённого объекта Это практически единственный случай, когда вам нужно явно вызывать деструктор. Когда я пишу деструктор, должен ли я явно вызывать деструкторы для объектов-членов моего класса? Нет. Никогда не надо явно вызывать деструктор (за исключением случая с синтаксисом размещения new. Деструктор класса (неявный, созданный компилятором, или явно описанный вами) автоматически вызывает деструкторы объектов-членов класса. Эти объекты уничтожаются в порядке обратном порядку их объявления в теле класса: class Member { public: ~Member(); class Fred { public: ~Fred(); private: Member x ; Member y ; Member z ; Fred::~Fred() Компилятор автоматически вызывает z .~Member() Компилятор автоматически вызывает y .~Member() Компилятор автоматически вызывает x .~Member() Когда я пишу деструктор производного класса, нужно ли мне явно вызывать деструктор предка? Нет. Никогда не надо явно вызывать деструктор (за исключением случая с синтаксисом размещения new). Деструктор производного класса (неявный, созданный компилятором, или явно описанный вами) автоматически вызывает деструкторы предков. Предки уничтожаются после уничтожения объектов-членов производного класса. В случае множественного наследования непосредственные предки класса уничтожаются в порядке обратном порядку их появления в списке наследования. class Member { public: ~Member(); ... class Base { public: virtual ~Base(); Виртуальный деструктор[20.4] ... class Derived : public Base { public: ~Derived(); ... private: Member x ; Derived::~Derived() Компилятор автоматически вызывает x .~Member() Компилятор автоматически вызывает Base::~Base() Примечание: в случае виртуального наследования порядок уничтожения классов сложнее. Если вы полагаетесь на порядок уничтожения классов в случае виртуального наследования, вам понадобится больше информации, чем изложено здесь. Расскажите все-таки о пресловутых нулевых указателях Для каждого типа указателей существует (согласно определению языка) особое значение - нулевой указатель , которое отлично от всех других значений и не указывает на какой-либо объект или функцию. Таким образом, ни оператор &, ни успешный вызов malloc() никогда не приведут к появлению нулевого указателя. (malloc возвращает нулевой указатель, когда память выделить не удается, и это типичный пример использования нулевых указателей как особых величин, имеющих несколько иной смысл память не выделена или теперь ни на что не указываю .) Нулевой указатель принципиально отличается от неинициализированного указателя. Известно, что нулевой указатель не ссылается ни на какой объект; неинициализированный указатель может ссылаться на что угодно. В приведенном выше определении уже упоминалось, что существует нулевой указатель для каждого типа указателя, и внутренние значения нулевых указателей разных типов могут отличаться. Хотя программистам не обязательно знать внутренние значения, компилятору всегда необходима информация о типе указателя, чтобы различить нулевые указатели, когда это нужно. Как получить нулевой указатель в программе? В языке С константа 0, когда она распознается как указатель, преобразуется компилятором в нулевой указатель. То есть, если во время инициализации, присваивания или сравнения с одной стороны стоит переменная или выражение, имеющее тип указателя, компилятор решает, что константа 0 с другой стороны должна превратиться в нулевой указатель и генерирует нулевой указатель нужного типа. Следовательно, следующий фрагмент абсолютно корректен: char *p = 0; if(p != 0) Однако, аргумент, передаваемый функции, не обязательно будет распознан как значение указателя, и компилятор может оказаться не способным распознать голый 0 как нулевой указатель. Например, сис-
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |