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. При створенні першого екземпляру, внутрішня змінна count (кількість копій) встановлюється рівною в значення 0. Приріст цієї змінної можливий у випадках коли створюється копія покажчика. А копія може створюватись при виклику конструктора копіювання або оператора копіювання. Таким чином, відслідковується кількість створених копій “розумного” покажчика.

Найбільш важливим елементом класу є деструктор

~SmartPtr()
{
  // Якщо є об'єкт і немає копій,
  // то просто знищуємо цей об'єкт.
  if ((p != nullptr) && (count == 0))
  {
    delete[] p;
  }
  else
  {
    // інакше, просто знищується копія
    count--; // зменшити лічильник копій
  }
}

Як видно з коду деструктора, пам’ять для покажчика звільняється тільки один раз незалежно від кількості копій. Фактично, звільняється пам’ять для покажчика, пам’ять для якого була виділена перший раз. Якщо розглядається копія, то звільнення пам’яті не відбувається, зменшується тільки лічильник копій. Деструктор забезпечує правильне звільнення пам’яті у випадку, якщо існують копії “розумного” покажчика.

 


Споріднені теми