Глава 8
Вверх
Можно было бы попытаться решить проблему , описанную в предыдущей
программе 9.13 переписав функцию TheFunction() так, чтобы она создавала
объект Frisky в области динамической памяти. В этом случае после выхода
из функции TheFunction() объект будет жив.
Новый подход порождает новую проблему:что делать с памятью выделенной для
Frisky , когда он становится ненужным?
Эта проблема проиллюстрирована в следующей программе.
Утечка памяти.
// Listing 9.14
// Resolving memory leaks
class SimpleCat
{
private:
SimpleCat::SimpleCat(int age, int weight)
{
SimpleCat & TheFunction();
int main()
{
SimpleCat &TheFunction()
{
Результат:
Анализ:
Функция TheFunction() была изменена таким образом, чтобы больше не возвращать
ссылку на локальную переменную. В стр 41 выделяется необходимая область
динамически распределяемой памяти, и ее адрес присваивается указателю.
Этот объект, адресом которого инициализирован указатель, выводится на экран:
после чего ссылка на указатель объекта pFrisky класса SimpleCat возвращается
по ссылке.(возвращается указатель инициализированный адресом объекта):
Вот все эти три команды в программе друг за другом:
В стр 28 значение, возвращаемое функцией TheFunction() , присваивается ссылке
на объект класса SimpleCat, (адрес объекта возвращен функцией и
присваивается ссылке на объект):
затем этот объект(через ссылку на него) используется для получения
значения возраста кота,
Чтобы доказать что ссылка, объявляемая в функции main() , ссылается на объект,
который размещен в области динамической памяти, выделенной для него в теле
функции TheFunction() , к ссылке rCat применяется оператор взятия адреса(&).
Вполне закономерно что адрес объекта, на который ссылается rCat, совпадает
с адресом объекта, расположенного в свободной области памяти.
До сих пор все было нормально. Но как освободить область памяти, которая
больше не нужна?Ведь нельзя же выполнить операцию удаления для ссылки.
На ум приходит одно решение:создать указатель и инициализировать его адресом,
полученным из ссылки rCat.
При этом и память будет освобождена и утечка памяти предотвращена.
Все же одна маленькая проблема остается:на что ссылается переменная rCat
теперь, после выполнения строки 34?
Как уже говорилось ссылка всегда должна оставаться псевдонимом реального
объекта, если же она ссылается на нулевой объект(как в данном случае,
потому что объект, на который указывал указатель pCat, был уничтожен
командой delete ), о корректности программы говорить не приходится.
Для решения этой проблемы есть три пути.
-Первый состоит в объявлении объекта класса SimpleCat в строке 28 и
возвращении этого объекта из функции TheFunction() как значения.
-Второй-заключается в объявлении класса SimpleCat в области динамической
памяти (в теле функции TheFunction()) , но сделать это нужно так, чтобы
функция TheFunction() возвращала указатель на данный объект. Затем, когда
объект больше не нужен, его можно удалить в вызывающей функции с помощью
оператора delete.
-Третье решение возможно самое правильное -объявить объект в вызывающей
функции, а затем передать в функцию TheFunction() ссылку на него.
Вверх
При выделении программой области в динамической памяти возвращается
указатель, который очень важно сохранить, поскольку в случае его утраты эту
область памяти нельзя будет освободить, что приведет к ее утечке.
При передаче этого участка памяти между функциями кто то будет "обладать"
указателем. Как правило значение, содержащееся в этом участке, передается
в виде ссылки, а освобождать его должна функция, которая его создала. Но это
не догма , а лишь рекомендация для программистов.
Весьма небезопасно если объект в области динамической памяти создает одна
функция, а удаляет другая. Неопределенность относительно владельца указателя
чревата ошибками:можно забыть освободить память или попытаться освободить
ее дважды. Любой из этих случаев становится причиной больших неприятностей.
Именно поэтому целесообразно придерживаться правил и освобождать память
там, где она выделялась.
Вверх
Важно уяснить , что ссылки всегда должны быть инициализированы
существующими объектами и их нельзя переназначить до самого конца программы.
Ссылка представляет собой псевдоним объекта, и любое действие выполненное над
ссылкой, выполняется над ее адресатом. Доказательством этого может служить тот
факт, что при взятии адреса ссылки, возвращается адрес связанного с ней объекта.
Было доказано что передача объектов в функции в качесве ссылок может быть
более эффективной, чем передача их в качестве значений. Передача объектов по
ссылке позволяет вызываемой функции изменять значения переменных вызывающей
функции.
Аргументы, передаваемые функции, и значения возвращаемые из функций, могут
представлять собой ссылки, а реализовать это можно как с помощью указателей,
так и с помощью ссылок.
Рассматривалось использование указателей на постоянные объекты и постоянных
указателей на объекты с целью обеспечения безопасной передачи значений между
функциями или для повышения эффективности работы программ.
Назад |
Начало урока |
Вверх |
Вперед
Возвращение ссылки на объект в области динамической памяти
Внимание!Эта программа 9.14 компилируется, компонуется и начинает работать ,
но это мина замедленного действия, которая ожидает своего часа.
#include <iostream>
SimpleCat (int age, int weight);
~SimpleCat() {
int GetAge() {
int GetWeight() {
int itsAge;
int itsWeight;
itsWeight = weight;
int age = rCat.GetAge();
std::cout << "rCat is " << age << " years old!\n";
std::cout << "&rCat: " << &rCat << std::endl;
// How do you get rid of that memory?
SimpleCat * pCat = &rCat;
delete pCat;
// Uh oh, rCat now refers to ??
return 0;
std::cout << "pFrisky: " << pFrisky << std::endl;
return *pFrisky;
41 SimpleCat * pFrisky = new SimpleCat(5,9);
std::cout << "pFrisky: " << pFrisky << std::endl;
return *pFrisky;
41 SimpleCat * pFrisky = new SimpleCat(5,9);
std::cout << "pFrisky: " << pFrisky << std::endl;
return *pFrisky;
28 SimpleCat & rCat = TheFunction();
29 int age = rCat.GetAge();
которое выводится на экран в стр 30.
30 std::cout << "rCat is " << age << " years old!\n";
std::cout << "&rCat: " << &rCat << std::endl;
// Как бы избавиться от этой памяти?
SimpleCat * pCat = &rCat;
34 delete pCat;
// Ой, ой, на что же теперь ссылается rCat ??
Кто владеет указателем ?
Если пришлось создать функцию, которая требует выделения в области
динамической памяти, а затем возвращает ее в вызывающую функцию, то следует
пересмотреть концепцию программы.
Лучше сделать так, чтобы память выделяла вызывающая функция,
а затем освобождала ее после возвращения результата из вызываемой функции.
Это переносит все операции по обслуживанию памяти в одну функцию,
снимая вопрос о принадлежности указателя.
Резюме
Содержание