Возвращение объектов функциями



 

Если объекты можно передавать функциям, то "с таким же успехом" функции могут возвращать объекты. Чтобы функция могла вернуть объект, во‑первых, необходимо объявить в качестве типа возвращаемого ею значения тип соответствующего класса. Во‑вторых, нужно обеспечить возврат объекта этого типа с помощью обычной инструкции return . Рассмотрим пример функции, которая возвращает объект.

 

 

В этом примере функция input() создает локальный объект str класса sample , а затем считывает строку с клавиатуры. Эта строка копируется в строку str.s , после чего объект str возвращается функцией input() и присваивается объекту ob в функции main() .

 

Потенциальная проблема при возвращении объектов функциями

 

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

 

 

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

 

 

Обратите внимание на то, что деструктор класса sample вызывается три раза! В первый раз он вызывается при выходе локального объекта str из области видимости в момент возвращения из функции input() . Второй вызов деструктора ~sample() происходит тогда, когда разрушается временный объект, возвращаемый функцией input() . Когда функция возвращает объект, автоматически генерируется невидимый (для вас) временный объект, который хранит возвращаемое значение. В данном случае этот объект просто представляет собой побитовую копию объекта str , который является значением, возвращаемым из функции. Следовательно, после возвращения из функции выполняется деструктор временного объекта. Поскольку область памяти, выделенная для хранения строки, вводимой пользователем, уже была освобождена (причем дважды!), при вызове функции show() на экран выведется "мусор" . (Вы можете не увидеть вывод на экран "мусора" . Это зависит от того, как ваш компилятор реализует динамическое выделение памяти. Однако ошибка все равно здесь присутствует.) Наконец, по завершении программы вызывается деструктор объекта ob (в функции main() ). Ситуация здесь осложняется тем, что при первом вызове деструктора освобождается память, выделенная для хранения строки, получаемой функцией input() . Таким образом, само по себе плохо не только то, что остальные два обращения к деструктору класса sample попытаются освободить уже освобожденную область динамически выделяемой памяти, но они также могут разрушить систему динамического распределения памяти.

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

 

Создание и использование конструктора копии

 

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

Для начала еще раз сформулируем проблемы, для решения которых мы хотим определить конструктор копии. При передаче объекта функции создается побитовая (т.е. точная) копия этого объекта, которая передается параметру этой функции. Однако возможны ситуации, когда такая идентичная копия нежелательна. Например, если оригинальный объект содержит указатель на выделяемую динамически память, то и указатель, принадлежащий копии, также будет ссылаться на ту же область памяти. Следовательно, если копия внесет изменения в содержимое этой области памяти, эти изменения коснутся также оригинального объекта! Более того, при завершении функции копия будет разрушена (с вызовом деструктора). Это может нежелательным образом сказаться на исходном объекте.

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

Конструктор копии позволяет управлять действиями, составляющими процесс создания копии объекта.

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

Прежде чем подробнее знакомиться с использованием конструктора копии, важно понимать, что в C++ определено два отдельных вида ситуаций, в которых значение одного объекта передается другому. Первой такой ситуацией является присваивание, а второй – инициализация. Инициализация может выполняться тремя способами, т.е. в случаях, когда:

■ один объект явно инициализирует другой объект, как, например, в объявлении;

■ копия объекта передается параметру функции;

■ генерируется временный объект (чаще всего в качестве значения, возвращаемого функцией).

Конструктор копии применяется только к инициализациям. Он не применяется к присваиваниям.

Узелок на память. Конструкторы копии не оказывают никакого влияния на операции присваивания.

 

Конструктор копии вызывается в случае, когда один объект инициализирует другой.

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

 

 

Здесь элемент obj означает ссылку на объект, которая используется для инициализации другого объекта. Например, предположим, у нас есть класс myclass и объект y типа myclass , тогда при выполнении следующих инструкций будет вызван конструктор копии класса myclass .

 

 

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

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

 


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

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






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