Конструкторы, деструкторы и передача объектов



 

Несмотря на то что передача функциям несложных объектов в качестве аргументов – довольно простая процедура, при этом могут происходить непредвиденные события, имеющие отношение к конструкторам и деструкторам. Чтобы разобраться в этом, рассмотрим следующую программу.

 

 

При выполнении эта программа выводит следующие неожиданные результаты.

 

 

Как видите, здесь выполняется одно обращение к функции конструктора (при создании объекта a ), но почему‑то два обращения к функции деструктора. Давайте разбираться, в чем тут дело.

При передаче объекта функции создается его копия (и эта копия становится параметром в функции). Создание копии означает "рождение" нового объекта. Когда выполнение функции завершается, копия аргумента (т.е. параметр) разрушается. Здесь возникает сразу два вопроса. Во‑первых, вызывается ли конструктор объекта при создании копии? Во‑вторых, вызывается ли деструктор объекта при разрушении копии? Ответы могут удивить вас.

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

Но когда функция завершается и разрушается копия объекта, используемая в качестве аргумента, вызывается деструктор этого объекта. Необходимость вызова деструктора связана с выходом объекта из области видимости. Именно поэтому предыдущая программа имела два обращения к деструктору. Первое произошло при выходе из области видимости параметра функции display() , а второе– при разрушении объекта a в функции main() по завершении программы.

Итак, когда объект передается функции в качестве аргумента, обычный конструктор не вызывается. Вместо него вызывается конструктор копии, который по умолчанию создает побитовую (идентичную) копию этого объекта. Но когда эта копия разрушается (обычно при выходе за пределы области видимости по завершении функции), обязательно вызывается деструктор.

 

Потенциальные проблемы при передаче параметров

 

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

 

 

Вот как выглядят результаты выполнения этой программы.

 

 

Эта программа содержит принципиальную ошибку. И вот почему: при создании в функции main() объекта a выделяется область памяти, адрес которой присваивается указателю а.р . При передаче функции display() объект a копируется в параметр ob . Это означает, что оба объекта (a и ob ) будут иметь одинаковое значение для указателя р .

Другими словами, в обоих объектах (в оригинале и его копии) член данных p будет указывать на одну и ту же динамически выделенную область памяти. По завершении функции display() объект ob разрушается, и его разрушение сопровождается вызовом деструктора. Деструктор освобождает область памяти, адресуемую указателем ob.р . Но ведь эта (уже освобожденная) область памяти – та же самая область, на которую все еще указывает член данных (исходного объекта) a.p ! Налицо серьезная ошибка.

В действительности дела обстоят еще хуже. По завершении программы разрушается объект a , и динамически выделенная (еще при его создании) память освобождается вторично. Дело в том, что освобождение одной и той же области динамически выделенной памяти во второй раз считается неопределенной операцией, которая, как правило (в зависимости от того, как реализована система динамического распределения памяти), вызывает неисправимую ошибку.

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

 

 

Результаты выполнения этой версии программы выглядят гораздо лучше предыдущих.

 

 

Как видите, здесь деструктор вызывается только один раз, поскольку при передаче по ссылке аргумента функции display() копия объекта не создается.

Передача объекта по ссылке – прекрасное решение описанной проблемы, но только в случаях, когда ситуация позволяет принять его, что бывает далеко не всегда. К счастью, есть более общее решение: можно создать собственную версию конструктора копии. Это позволит точно определить, как именно следует создавать копию объекта и тем самым избежать описанных выше проблем. Но прежде чем мы займемся конструктором копии, имеет смысл рассмотреть еще одну ситуацию, в обработке которой мы также можем выиграть от создания конструктора копии .

 


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

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






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