Умный указатель для начинающих с использованием счетчика

в 20:08, , рубрики: c++, smart pointer, умный указатель, метки: ,

Статья пишется с единственной целью — дать начинающим базовое представление об умных указателях. Я осведомлен в том, что статьи такого рода уже существуют, но они едва ли дают новичку понимание действия данного инструменты. Мне ни в коем случае не хочется журить других авторов, дело только в том, что многие упоминают о самой реализации умного указателя только вскользь, все больше толкуя о существующих инструментах, хронологии их появления, преимуществах и недостатках. Мне же хотелось бы, чтобы студент-первокурсник, делая лабораторную работу, получил готовую и простую реализацию умного указателя с пакетом объяснений в придачу.

Вступление

А начнем мы с начала: в общем случае умный указатель — это некоторая надстройка над обыкновенным указателем, которая добавляет в него полезную или требуемую функциональность. Значит это все то, что он должен предоставлять все те же возможности по работе с указателем (разыменование, получение доступа к полям или функциям из-под указателя) и заниматься «грязной работой» — предотвращать утечки, избегать обращения к ранее освобожденному участку памяти. Хотелось бы сразу сказать, что мне случалось видеть создание объектов умного указателя только для адресов, под которыми лежат объекты классов, что верно, ведь зачистки памяти под указателями подошел бы и обычный MemoryGuard, которому бы хватило одного delete.

Ближе к делу

Итак, перейдем непосредственно к реализации Shared Pointer. Сперва вести учет ресурсов не будем. Требования к классу будут следующие:

— Наличие конструктора, принимающего указатель на ресурс.
— Перегруженные операторы -> и *.
— Акссесор, позволяющий получить адрес ресурса.

Поскольку класс шаблонный, описание и реализацию можно выполнить в одному hpp-файле. Операторы -> и * могут быть определенные как члены класса, ведь слева от них будет всегда находится объект нашего умного указателя.

template<class T>
class shr_ptr
{
        T* resource;
public:
        shr_ptr(T* res=NULL):resource(res)
        {     
        } 
        ~shr_ptr()
        {
                delete resource;
        }
        T* operator ->() const 
        {
                return resource;
        }
        T& operator * () const 
        {
                return *resource;
        }
        T* get () const 
        {
                return resource;
        }
};

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

    int main()
    {
        {
            shr_ptr <SomeClass> sh (new SomeClass);
            sh->do_operation();
        }
        if(_CrtDumpMemoryLeaks())
            cout<<"LEAKS";
    }

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

template<class T>
class shr_ptr
{
        T* resource;
        int* count 
public:
        shr_ptr():resource(NULL),count(NULL)
        {
                
        } 

        shr_ptr(T* res):resource(res)
        {
                count = new int(1);
        } 

        shr_ptr(const shr_ptr<T>& R):
        {
                if(R.resource!=NULL)
		{
			resource = R.resource;
			count = R.count;
			*count = *R.count+1;
		}
        } 

        ~shr_ptr()
        {
                if(resource!=NULL && --*(count)==0)
		{
			delete resource;
			delete count;
			resource = NULL;
		}
        }
       
       shr_ptr<T>& operator = (const shr_ptr<T>& R)
       {
                if(R.resource != this->resource)
		{
			 shr_ptr<T> tmp (R);
			char sBuff[sizeof( shr_ptr<T>)];
			memcpy(sBuff,this,sizeof(shr_ptr<T>));
			memcpy(this,&tmp,sizeof(shr_ptr<T>));
			memcpy(&tmp,sBuff,sizeof( shr_ptr<T>));
			return *this;
		}

       }

        T* operator ->() const 
        {
                return resource;
        }

        T& operator * () const 
        {
                return *resource;
        }

        T* get () const 
        {
                return resource;
        }

};

Как вы можете видеть, счетчик создается для каждого нового ресурса, увеличивается при повторном создании и уменьшается при вызове деструктора.

После этого создав в main несколько объектов умного указателя с одинаковыми ресурсами мы гарантировано очистим память только один раз, то есть программа успешно отработает. Все благодаря нехитрому счетчику. Пример:

    int main()
    {
        {
            shr_ptr <SomeClass> sh (new SomeClass);
            shr_ptr <SomeClass> sh1(sh);
            shr_ptr <SomeClass> sh2 (new SomeClass);
            sh = sh2; 

            sh->do_operation();
            sh1->do_operation();
            sh2->do_operation();
        }
        if(_CrtDumpMemoryLeaks())
            cout<<"LEAKS";
    }

Спасибо за внимание!

Автор: kartograph

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js