C++. Разработка класса реализующего «умный» указатель

Разработка класса реализующего «умный» указатель (smart pointer)

В данной теме приводится пример разработки шаблонного класса, реализующего «умный» указатель.


Содержание


Поиск на других ресурсах:

1. Понятие «умного» указателя. Особенности реализации

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

  • проверки на корректные пределы использования указателя;
  • подсчет количества копий указателя;
  • освобождение памяти, на которую указывает указатель и т.д.

Использование «умного» указателя делает невозможным случай попытки двойного освобождения того же участка памяти. Бывают случаи, когда на один участок памяти указывают два или более разных указателей. После освобождения этой памяти в одном из указателей попытка освобождения через другие указатели приведет к генерированию исключительной ситуации и «вылета» программы. Если в программе использовать специально разработанный класс «умного» указателя, эта ситуация будет обработана корректно.

С точки зрения реализации в классе, для «умного» указателя определяют следующие внутренние данные (поля):

  • указатель на объект заданного типа. Если это обобщенный класс, то указатель на объект обобщенного типа T;
  • счетчик количества обращений к объекту.

Для обеспечения минимальных возможностей «умного» указателя в классе нужно реализовать как минимум одну операторную функцию operator->(), которая обеспечивает доступ по указателю.

 

2. Пример шаблонного класса «умного» указателя

Ниже представлен программный код двух классов:

  • класс Int, реализующий некоторое целое число. Этот класс разработан с целью демонстрации. По желанию можно использовать любой другой класс;
  • шаблонный класс SmartPtr, реализующий «умный» указатель.

 

#include <iostream>
using namespace std;

// Тема: умные указатели
// Задан некоторый класс
class Int
{
private:
  int d;

public:
  // Конструктор
  Int(int _d) : d(_d) { }

  // Методы доступа
  int Get() { return d; }
  void Set(int _d)
  {
    d = _d;
  }

  // Метод Print()
  void Print(string msg)
  {
    cout << msg.c_str() << d << endl;
  }
};

// Класс умного указателя на обобщенный тип T
template <typename T>
class SmartPtr
{
private:
  T* p; // указатель на обобщенный тип T
  int count; // количество копий

public:
  // Конструктор
  SmartPtr(T* _p = nullptr)
  {
    // Записываем 0 - копий нет
    count = 0;
    p = _p;
  }

  // Конструктор копирования
  SmartPtr(const SmartPtr& obj)
  {
    // Создается копия
    p = obj.p;

    // Увеличить счетчик
    count++;
  }

  // Оператор копирования
  SmartPtr operator=(const SmartPtr& obj)
  {
    // Создается копия
    p = obj.p;
    count++;
    return *this;
  }

  // Деструктор - уничтожает объект-оригинал,
  // объекты-копии не уничтожаются.
  ~SmartPtr()
  {
    // Если есть объект и нету копий,
    // то просто уничтожаем это объект.
    if ((p != nullptr) && (count == 0))
    {
      cout << "Delete object" << endl;
      delete[] p;
    }
    else
    {
      // иначе, просто уничтожается копия
      cout << "Delete copy" << endl;
      count--; // уменьшить счетчик копий
    }
  }

  // Переопределить оператор -> доступа по указателю
  T* operator->()
  {
    return p;
  }
};

void main()
{
  // 1. Создать объект класса Int
  Int* obj1 = new Int(10);
  obj1->Print("obj1: ");

  // 2. Инициализировать этим объектом умный указатель
  SmartPtr<Int> ptr(obj1);
  ptr->Print("ptr->obj: ");

  // 3. Создать копию умного указателя
  SmartPtr<Int> ptr2 = ptr; // вызывается конструктор копирования
  ptr2->Print("ptr2->obj: ");

  // 4. Создать еще одну копию умного указателя
  SmartPtr<Int> ptr3;
  ptr3 = ptr2; // вызывается оператор копирования
  ptr3->Print("ptr3->obj: ");
}

После запуска на выполнения программа выдаст следующий результат

obj1: 10
ptr->obj: 10
ptr2->obj: 10
ptr3->obj: 10

 

3. Анализ программного кода

Шаблонный класс SmartPtr<T> оперирует обобщенным типом T. В классе объявляется конструктор, принимающий указатель на тип T. При создании первого экземпляра, внутренняя переменная count (количество копий) устанавливается равной в значение 0. Приращение этой переменной возможно в случаях когда создается копия указателя. А копия может создаваться при вызове конструктора или оператора копирования. Таким образом, отслеживается количество созданных копий «умного» указателя.

Наиболее важным элементом класса является деструктор

~SmartPtr()
{
  // Если есть объект и нету копий,
  // то просто уничтожаем этот объект.
  if ((p != nullptr) && (count == 0))
  {
    delete[] p;
  }
  else
  {
    // иначе, просто уничтожается копия
    count--; // уменьшить счетчик копий
  }
}

Как видно из кода деструктора, память для указателя освобождается только один раз вне зависимости от количества копий. Фактически, освобождается память для указателя, память для которого была выделена в первый раз. Если рассматривается копия, то освобождения памяти не происходит, уменьшается только счетчик копий. Деструктор обеспечивает правильное освобождение памяти в случае, если есть копии «умного» указателя.

 


Связанные темы