Назад | Начало урока | Вперед
Содержание

Глава 8

Вверх

Возвращение ссылки на объект в области динамической памяти

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

Новый подход порождает новую проблему:что делать с памятью выделенной для Frisky , когда он становится ненужным?

Эта проблема проиллюстрирована в следующей программе.


Внимание!Эта программа 9.14 компилируется, компонуется и начинает работать , но это мина замедленного действия, которая ожидает своего часа.

Утечка памяти.

// Listing 9.14 // Resolving memory leaks

#include <iostream>

class SimpleCat {

public:
SimpleCat (int age, int weight);
~SimpleCat() {
}
int GetAge() {
return itsAge;
}
int GetWeight() {
return itsWeight;
}

private:
int itsAge;
int itsWeight;

};

SimpleCat::SimpleCat(int age, int weight) {

itsAge = age;
itsWeight = weight;
}

SimpleCat & TheFunction();

int main() {

28 SimpleCat & rCat = TheFunction();
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;
}

SimpleCat &TheFunction() {

41 SimpleCat * pFrisky = new SimpleCat(5,9);
std::cout << "pFrisky: " << pFrisky << std::endl;
return *pFrisky;
}

Результат:

Анализ:

Функция TheFunction() была изменена таким образом, чтобы больше не возвращать ссылку на локальную переменную. В стр 41 выделяется необходимая область динамически распределяемой памяти, и ее адрес присваивается указателю.

41 SimpleCat * pFrisky = new SimpleCat(5,9);

Этот объект, адресом которого инициализирован указатель, выводится на экран:

std::cout << "pFrisky: " << pFrisky << std::endl;

после чего ссылка на указатель объекта pFrisky класса SimpleCat возвращается по ссылке.(возвращается указатель инициализированный адресом объекта):

return *pFrisky;

Вот все эти три команды в программе друг за другом:

41 SimpleCat * pFrisky = new SimpleCat(5,9);
std::cout << "pFrisky: " << pFrisky << std::endl;
return *pFrisky;

В стр 28 значение, возвращаемое функцией TheFunction() , присваивается ссылке на объект класса SimpleCat, (адрес объекта возвращен функцией и присваивается ссылке на объект):

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;

Чтобы доказать что ссылка, объявляемая в функции main() , ссылается на объект, который размещен в области динамической памяти, выделенной для него в теле функции TheFunction() , к ссылке rCat применяется оператор взятия адреса(&).

Вполне закономерно что адрес объекта, на который ссылается rCat, совпадает с адресом объекта, расположенного в свободной области памяти.

До сих пор все было нормально. Но как освободить область памяти, которая больше не нужна?Ведь нельзя же выполнить операцию удаления для ссылки.

На ум приходит одно решение:создать указатель и инициализировать его адресом, полученным из ссылки rCat.
// Как бы избавиться от этой памяти?

SimpleCat * pCat = &rCat; 34 delete pCat; // Ой, ой, на что же теперь ссылается rCat ??

При этом и память будет освобождена и утечка памяти предотвращена.

Все же одна маленькая проблема остается:на что ссылается переменная rCat теперь, после выполнения строки 34?

Как уже говорилось ссылка всегда должна оставаться псевдонимом реального объекта, если же она ссылается на нулевой объект(как в данном случае, потому что объект, на который указывал указатель pCat, был уничтожен командой delete ), о корректности программы говорить не приходится.


Иногда программа, содержащая ссылку на нулевой объект может быть откомпилирована, но она все равно останется некорректной, а результаты ее работы будут непредсказуемы.

Для решения этой проблемы есть три пути.

-Первый состоит в объявлении объекта класса SimpleCat в строке 28 и возвращении этого объекта из функции TheFunction() как значения.

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

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

Вверх

Кто владеет указателем ?

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


Если пришлось создать функцию, которая требует выделения в области динамической памяти, а затем возвращает ее в вызывающую функцию, то следует пересмотреть концепцию программы.
Лучше сделать так, чтобы память выделяла вызывающая функция, а затем освобождала ее после возвращения результата из вызываемой функции. Это переносит все операции по обслуживанию памяти в одну функцию, снимая вопрос о принадлежности указателя.


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

Не передавайте ссылки на объекты, которые могут выйти из области действия. Не используйте ссылки на нулевые объекты.

Вверх

Резюме

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

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

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

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


Назад | Начало урока | Вверх | Вперед
Содержание

Hosted by uCoz