Язык программирования C++

"Интеллигентный указатель"


Рассмотрим еще один пример использования класса-шаблона. С его помощью мы попытаемся " усовершенствовать" указатели языка Си++. Если указатель указывает на объект, выделенный с помощью операции new, необходимо явно вызывать операцию delete тогда, когда объект становится не нужен. Однако далеко не всегда просто определить, нужен объект или нет, особенно если на него могут ссылаться несколько разных указателей. Разработаем класс, который ведет себя очень похоже на указатель, но автоматически уничтожает объект, когда уничтожается последняя ссылка на него. Назовем этот класс "интеллигентный указатель" (Smart Pointer). Идея заключается в том, что настоящий указатель мы окружим специальной оболочкой. Вместе со значением указателя мы будем хранить счетчик – сколько других объектов на него ссылается. Как только значение этого счетчика станет равным нулю, объект, на который указатель указывает, пора уничтожать.

Структура Ref хранит исходный указатель и счетчик ссылок.

template class T struct Ref { T* realPtr; int counter; };

Теперь определим интерфейс "интеллигентного указателя":

template class T class SmartPtr { public: // конструктор из обычного указателя SmartPtr(T* ptr = 0); // копирующий конструктор SmartPtr(const SmartPtr s); ~SmartPtr(); SmartPtr operator=(const SmartPtr s); SmartPtr operator=(T* ptr); T* operator-() const; T operator*() const; private: RefT* refPtr; };

У класса SmartPtr определены операции обращения к элементу -, взятия по адресу "*" и операции присваивания. С объектом класса SmartPtr можно обращаться практически так же, как с обычным указателем.

struct A { int x; int y; }; SmartPtrA aPtr(new A); int x1 = aPtr-x; (*aPtr).y = 3;

// создать новый указатель // обратиться к элементу A // обратиться по адресу

Рассмотрим реализацию методов класса SmartPtr. Конструктор инициализирует объект указателем. Если указатель равен нулю, то refPtr устанавливается в ноль. Если же конструктору передается ненулевой указатель, то создается структура Ref, счетчик обращений в которой устанавливается в 1, а указатель – в переданный указатель:


template class T SmartPtrT::SmartPtr(T* ptr) { if (ptr == 0) refPtr = 0; else { refPtr = new RefT; refPtr-realPtr = ptr; refPtr-counter = 1; } }

Деструктор уменьшает количество ссылок на 1 и, если оно достигло 0, уничтожает объект

template class T SmartPtr T::~SmartPtr() { if (refPtr != 0) { refPtr-counter--; if (refPtr-counter = 0) { delete refPtr-realPtr; delete refPtr; } } }

Реализация операций - и * довольно проста:

template class T T* SmartPtrT::operator-() const { if (refPtr != 0) return refPtr-realPtr; else return 0; } template class T T SmartPtrT::operator*() const { if (refPtr != 0) return *refPtr-realPtr; else throw bad_pointer; }

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

template class T SmartPtrT::SmartPtr(const SmartPtr s):refPtr(s.refPtr) { if (refPtr != 0) refPtr-counter++; }

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

template class T SmartPtr SmartPtrT::operator=(const SmartPtr s) { // отсоединиться от имеющегося указателя if (refPtr != 0) { refPtr-counter--; if (refPtr-counter = 0) { delete refPtr-realPtr; delete refPtr; } } // присоединиться к новому указателю refPtr = s.refPtr; if (refPtr != 0) refPtr-counter++; }

В следующей функции при ее завершении объект класса Complex будет уничтожен:

void foo(void) { SmartPtrComplex complex(new Complex); SmartPtrComplex ptr = complex; return; }


Содержание раздела