Материал: OOP_LW_1_Umnye_ukazateli_Tranzaktsii

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

«ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ». ЧАСТЬ 2.

ЛАБОРАТОРНАЯ РАБОТА №1 ИСПОЛЬЗОВАНИЕ SMART-УКАЗАТЕЛЕЙ, МЕХАНИЗМА

ТРАНЗАКЦИЙ И ИЕРАРХИИ КЛАССОВ

Цель работы: применить знания, полученные в предыдущих работах, к реализации практических задач (реализация smart-указателей, механизма транзакций, иерархии классов).

ДИНАМИЧЕСКАЯ ПАМЯТЬ. ИНТЕЛЛЕКТУАЛЬНЫЕ УКАЗАТЕЛИ

Для управления динамической памятью в языке C++ используются два оператора: оператор new, который резервирует (а при необходимости и инициализирует) объект в динамической памяти и возвращает указатель на него; оператор delete, который получает указатель на динамический объект и удаляет его, освобождая зарезервированную память.

Работа с динамической памятью проблематична, поскольку сложно гарантировать освобождение памяти в нужный момент. Если забыть освобождать память вовремя, то появится утечка памяти, если освободить область памяти слишком рано, пока еще есть указатели на нее, то получится указатель на несуществующую область памяти.

Интеллектуальный указатель (smart pointer) действует, как обычный указатель, но с важным дополнением: автоматически удаляет объект, на который он указывает. Умный указатель обычно является шаблонным классом. Чаще всего умный указатель инкапсулирует семантику владения ресурсом. В таком случае он называется владеющим указателем. Владеющие указатели применяются для борьбы с утечками памяти и висячими ссылками.

Утечкой памяти (memory leak) называется ситуация, когда в программе нет ни одного указателя, хранящего адрес объекта, созданного в динамической памяти.

Висячей ссылкой или висячим указателем (dangling pointer, wild pointer, dangling reference) называется указатель, ссылающийся на уже удалённый объект (или не ссылающийся на допустимый объект соответствующего типа).

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

Библиотека С++ определяет два вида интеллектуальных указателей, отличающихся способом управления своими базовыми указателями: указатель std::shared_ptr позволяет нескольким указателям указывать на тот же объект, а указатель std::unique_ptr — нет. В отличие от указателя std::shared_ptr, только один указатель типа std::unique_ptr может одновременно указывать на конкретный объект. Библиотека языка С++ определяет также сопутствующий класс std::weak_ptr, являющийся второстепенной ссылкой на объект, управляемый указателем std::shared_ptr. Указатель std::weak_ptr моделирует временное владение: когда объект должен быть доступен только если он существует и может быть удален в любой момент кем-то другим. Интеллектуальные указатели определены в пространстве имен std в файле заголовка <memory>.

Интеллектуальные указатели чрезвычайно важны для идиомы программирования

RAII или Resource Acquisition Is Initialialization — получение ресурса является инициализацией. То есть, при получении какого-либо ресурса, его инициализируют в конструкторе, а, поработав с ним, корректно освобождают в деструкторе.

Ресурсом может быть что угодно, к примеру файл, сетевое соединение или блок памяти.

class VideoBuffer { int* myPixels;

public: VideoBuffer() {

myPixels = new int[640 * 480];

}

void makeFrame() { /* Работаем с фреймом */ } ~VideoBuffer() {

delete[] myPixels;

}

};

int game() { VideoBuffer screen; screen.makeFrame();

}

Главная задача идиомы RAII— обеспечить, чтобы одновременно с получением ресурса производилась инициализация объекта, чтобы все ресурсы для объекта создавались и подготавливались в одном блоке кода. На практике основным принципом RAII является предоставление владения любым ресурсом в куче (например, динамически выделенной памятью или дескрипторами системных объектов) объекту, выделенному стеком. Деструктор используемого ресурса должен содержать код удаления или освобождения всех задействованных объектов, а также весь необходимый код очистки.

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

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

//Пример №1. Пример использования smart pointer #include <iostream>

#include <locale.h> using namespace std;

class Employee {// класс, количество объектов которого должно отслеживаться

int age;

string department; string position;

public:

void setDepartment(string department){ this->department = department;

}

void showDepartment() { cout << "Сотрудник работает в подразделении:" << department << endl; }

void showPosition() { cout << "Сотрудник работает на должности:" << position << endl; }

};

template <class T> struct Status {

T* ptr; // указатель на объект

int counter; // счетчик числа ссылок на объект

}; //Класс умного указателя. Цель умного указателя состоит в подсчете

количества ссылок

//через множество умных указателей на один объект template <class T>

class SmartPointer { Status<T>* smartPtr;

public:

SmartPointer(T* ptr = 0); SmartPointer(const SmartPointer&); ~SmartPointer();

SmartPointer& operator=(const SmartPointer&); //перегрузка оператора

=

T* operator->() const; // перегрузка оператора -> void showCounter() { cout<<smartPtr->counter; }

}; //конструктор умного указателя

template <class T> SmartPointer<T>::SmartPointer(T* ptr) {

if (!ptr) smartPtr = NULL; // указатель на объект пустой else {

smartPtr = new Status<T>;

smartPtr->ptr = ptr; // инициализирует объект указателем smartPtr->counter = 1; // счетчик «прикрепленных» объектов

инициализируем единицей

}

}

//конструктор копирования умного указателя template <class T>

SmartPointer<T>::SmartPointer(const SmartPointer& obj) :smartPtr(obj.smartPtr) {

if (smartPtr) smartPtr->counter++; // только увеличение числа ссылок

}

//деструктор умного указателя template <class T> SmartPointer<T>::~SmartPointer() {

if (smartPtr) {

smartPtr->counter--; // уменьшается число ссылок на объект if (smartPtr->counter <= 0) { // если число ссылок на объект

меньше либо равно нулю, то уничтожается объект

delete smartPtr->ptr;//ссылка на объект уничтожается delete smartPtr;

}

}

}

template <class T>

T* SmartPointer<T>::operator->() const { if (smartPtr) return smartPtr->ptr; else return NULL;

}

template <class T>

SmartPointer<T>& SmartPointer<T>::operator=(const SmartPointer& obj) { if (smartPtr) {

smartPtr->counter--; // уменьшаем счетчик «прикрепленных»

объектов

if (smartPtr->counter <= 0) { // если объектов нет, то выполняется освобождается выделенная память

delete smartPtr->ptr; delete smartPtr;

}

}

smartPtr = obj.smartPtr; // присоединение к новому указателю if (smartPtr) smartPtr->counter++; // увеличить счетчик

«прикрепленных» объектов return *this;

}

int main() {

setlocale(LC_ALL, "Russian"); SmartPointer<Employee> employeeIT(new Employee); employeeIT->setDepartment("Отдел аналитики"); employeeIT->showDepartment(); SmartPointer<Employee> employeeAdmin(new Employee);

SmartPointer<Employee> employeeService = employeeIT; employeeIT.showCounter(); employeeAdmin.showCounter(); employeeService.showCounter();

employeeAdmin = employeeIT; employeeAdmin.showCounter(); return 0;

//когда область видимости закончится, объект будет удален

}

Результаты работы программы:

ИСПОЛЬЗОВАНИЕ ТРАНЗАКЦИЙ

Концепция smart-указателей позволяет просто решать задачу поддержки транзакций.

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

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

если клиент начал и не завершил транзакцию, то другие клиенты не видят его изменений;

две транзакции не могут одновременно менять одни и те же данные.

Для поддержки механизма транзакции объект должен содержать два указателя (на текущий объект и на объект, представляющий его предыдущее состояние) и