Розумний покажчик для початківців з використанням лічильника

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

Вступ
А почнемо з початку: у загальному випадку розумний покажчик — це певна надбудова над звичайним покажчиком, яка додає в нього корисну або необхідну функціональність. Значить це все те, що він повинен надавати всі ті ж можливості по роботі з покажчиком (розіменування, отримання доступу до полів або функцій з-під покажчика) і займатися брудною роботою» — запобігати витоку, уникати звернення до раніше звільненому ділянці пам'яті. Хотілося б відразу сказати, що мені траплялося бачити створення об'єктів розумного покажчика тільки для адрес, під якими лежать об'єкти класів, що вірно, адже зачистки пам'яті під покажчиками підійшов би і звичайний 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 ®;
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";
}

Спасибі за увагу!
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.