Классы поддерживающие транзакции

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

§ Если транзакция началась, то все изменения в ней либо вносятся вместе, либо не вносятся вообще;

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

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

Если требуется закрепить или отменить выполненные изменения, то необходимо хранить состояние объекта на заданный момент - начало транзакции, и в момент принятия решения или уничтожать предыдущее состояние (закрепление) или возвращаться к нему (отмена). Для этого может быть использован smart-указатель. Основой его являются два указателя - один на текущий объект, а второй - на объект, представляющий его предыдущее состояние, и три функции - старт, закрепление, отмена.

#include <iostream.h>

template <class T1>

Class Cls

{ T1 x;

public:

Cls() : x(0){}

f(int X) {x=X;}

T1 Show()

{return xl;}

};

// smart-pointer с поддержкой отмены.

template <class T2>

Class Tran

{ T2* that; // текущее значение объекта класса Cls

T2* prev; // предыдущее значение объекта класса Cls

public:

Tran():prev(NULL), that(new T2){}; // конструктор

Tran(const Tran & obj): // конструктор копирования

that(new T2(*(obj.that))), prev(NULL) {};

~Tran(){delete that; delete prev;}; // деструктор

Tran& operator=(const Tran & obj); // перегрузка присваивания

void Show(); // отображение значений (предыдущего и

// текущего) объекта класса Cls

void BeginTrans(); // начало транзакции

void Commit(); // закрепление

void DeleteTrans(); // отмена транзакции

T2* operator->(); // реализация указателя

};

template <class T2>

Tran<T2>& Tran<T2>::operator=(const Tran<T2> & obj)

{ if (this != &obj) // проверка на случай obj=obj

{ delete that; // удаление текущего значения объекта

that = new T2(*(obj.that)); // создание и копирование

}

return *this;

}

template <class T2>

T2* Tran<T2>::operator->() {return that;}

template <class T2>

void Tran<T2>::Show()

{ cout<<"состояния объекта"<<endl;

if (prev) cout<<"prev = "<<prev->get()<<endl;

cout<<"that = "<<that->get()<<endl;

}

template <class T2>

void Tran<T2>::BeginTrans()

{ delete prev; // удаление предыдущего значения

prev = that; // текущее становится предыдущим

that = new T2(*prev); // новое значение текущего значения

}

template <class T2>

void Tran<T2>::Commit ()

{ delete prev; // удаление предыдущего значения

prev = NULL; // предыдущего состояния нет

}

template <class T2>

void Tran<T2>::DeleteTrans()

{ if (prev != NULL)

{ delete that; // удаление текущего значения

that = prev; // предыдущее становится текущим

prev = NULL; // предыдущего состояния нет

}

}

int main (void)

{ Tran<Cls<int> > tr;

tr->f(5);

tr.Show();

tr.BeginTrans();

tr.Show();

tr->f(7);

tr.Show();

tr.DeleteTrans();

tr.Show();

tr.BeginTrans();

tr.Show();

tr->f(7);

tr.Commit();

tr.Show();

return 0;

}

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

состояния объекта

that = 5

состояния объекта

prev = 5

that = 5

состояния объекта

prev = 5

that = 7

состояния объекта

that = 5

состояния объекта

prev = 5

that = 5

состояния объекта

that = 7

Следует отметить, что перегрузка операции присваивания (=) понадобится в случае, например, если описаны два объекта.

Tran<Cls<int> > tr,ts;

. . .

ts=tr; // требуется перегрузка операции =

Задание значений параметров класса по умолчанию

Если в функцию сортировки числовой информации, принадлежащей некоторому классу, передавать символьные строки (char *), то желаемый результат (отсортированные строки) не будет получен. Как известно, в этом случае произойдет сравнение указателей, а не строк.

#include "iostream.h"

#include "string.h"

#include "typeinfo.h"

Class CompareNumb

{ public:

static bool sravn(int a, int b){return a<b;}

};

Class CompareString

{ public:

static bool sravn(char *a, char *b){return strcmp(a,b)<0;}

};

template <class T,class Compare>

Class vect

{ T *ms;

int size;

public:

vect(int SIZE):size(SIZE)

{ ms=new T[size];

const type_info & t=typeid(T); // получение ссылки t на

const char* s=t.name(); // объект класса type_info

for(int i=0;i<size;i++) // в описании типа

if(!strcmp(s,"char *"))

cin >> (*(ms+i)=(T)new char[20]); // ввод символьных строк

else cin >> *(ms+i); // ввод числовой информации

}

void sort_vec(vect<T,Compare> &);

};

template <class T,class Compare>

void vect<T,Compare>::sort_vec(vect<T,Compare> &vec)

{ for(int i=0;i<size-1;i++)

for(int j=i;j<size;j++)

if(Compare::sravn(ms[i],ms[j]))

{ T tmp=ms[i];

ms[i]=ms[j];

ms[j]=tmp;

}

for(i=0;i<size;i++) cout << *(ms+i) << endl;

};

Класс Compare должен содержать логическую функцию sravn(), сравнивающую два значения типа Т.

void main()

{ vect<int,CompareNumb> vec1(3);

vec1.sort_vec(vec1);

vect<char *,CompareString> vec2(3);

vec2.sort_vec(vec2);

}

Нетрудно заметить, что для всех типов, для которых операция меньше (<) имеет нужный смысл, можно написать следующий шаблон класса сравнения.

template<class T>

Class Compare

{ public:

static bool sravn(T a, T b)

{ const type_info & t=typeid(T); // получение ссылки t на

const char* s=t.name(); // объект класса type_info

if(!strcmp(s,"char *"))

return strcmp((char *)a,(char *)b)<0;

else return a<b;

}

};

template <class T,class Compare>

Class vect

{

// класс vect приведен выше

};

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

template <class T,class C = Compare<T> >

void vect<T,C>::sort_vec(vect<T,C> &vec)

{ for(int i=0;i<size-1;i++)

for(int j=i;j<size;j++)

if(C::sravn(ms[i],ms[j]))

{ T tmp=ms[i];

ms[i]=ms[j];

ms[j]=tmp;

}

for(i=0;i<size;i++) cout << *(ms+i) << endl;

};

void main()

{ vect<int,Compare<int> > vec1(3);

vec1.sort_vec(vec1);

vect<char *,Compare<char *> > vec2(3);

vec2.sort_vec(vec2);

vect<long,Compare<long> > vec3(3);

vec3.sort_vec(vec3);

}

В инструкции vect<int,Compare<int> > vec1(3) содержится пробел между угловыми скобками. При его отсутствии компилятор спутает >> с операцией >> (сдвига).

Свойства в С++

В общем случае, свойство это пара функций (public), одна из которых отвечает за установку компонент-данных (private) объекта, а другая за их считывание. Такое решение позволяет обеспечить инкапсуляцию данных. Необходимость использования свойств возникает тогда, когда при изменении некоторого параметра требуется произвести ещё некоторые действия.

В языках программирования (таких как "Visual Basic" или "Delphi"), обращение к свойствам объекта производится оператором присваивания, как при обращении к компонентам-данным класса в C++.

оbj.data = value

производится неявный вызов функции.

Наиболее простой способ обеспечения инкапсуляции в C++ заключается в написании пары функций типа get_val() и put_val() для каждого параметра. Заметим, что именно так реализованы свойства в технологии Automation. Это можно продемонстрировать на следующем примере простого класса:

Class cls

{ int m;

public:

int get_val()

{ return m; }

void put_val(int val)

{ m = val; }

};

В этом случае для обращения к такому свойству программист должен написать вызов соответствующей функции.

Разработчики Microsoft Visual C++ добавили в синтаксис языка несколько конструкций, позволяющих использовать свойства в операторах присваивания и вообще обращению с ними, как с компонентами-данными. В частности, модификатор _declspec получил дополнительный параметр "property". Это позволяет в классе объявить "виртуальную" переменную и связать её с соответствующими функциями. Теперь класс может выглядеть примерно так:

class cls

{ int m;

public:

_declspec(property(get=get_val, put=put_val)) int V;

int get_val()

{ return m; }

void put_val(int v)

{ m = v; }

};

В секции public содержится строка

_declspec(property(get=get_val, put=put_val)) int V;

в которой объявляется "виртуальная" переменная V типа int, при обращении к которой фактически будут вызваться функции get_val и put_val. Теперь доступ к данным объекта класса cls может быть выполнен следующим образом:

cls obj;

obj.V = 50; // при этом выполняется вызов put_val()

int k = obj.V; // при этом выполняется вызов get_val()

Модификатор _declspec(property) был введён для встроенной в компилятор поддержки технологии СОМ. Дело в том, что директива импорта библиотеки типа (что бы знать, что это такое, читайте книжки по COM) #import заставляет компилятор VC автоматически генерировать вспомогательные классы-обёртки для объектов СОМ. По аналогии с Visual Basic свойства сделаны индексными. Для этого, после объявления "виртуальной переменной" требуется поставить квадратные скобки:

_declspec(property(get=get_val, put=put_val)) int V [];

После этого свойство V может принимать один или несколько параметров-индексов, передаваемых в квадратных скобках. Так, например, вызов

obj.V["строка"] = 50;

Будет преобразован в вызов функции

obj.put_val( "строка", 50);

Основной недостаток описанного выше способа использования свойств в C++ является его зависимость от компилятора. Впрочем, другой, не менее известный компилятор "Borland C++ Builder" реализует концепцию свойств далёким от стандарта способом. В любом случае часто требуется (или хочется) достичь независимости от компилятора и соответствия кода программы стандарту C++. В тоже время язык C++ позволяет реализовать концепцию свойств. Для этого необходимо воспользоваться шаблонами и переопределить операторы присваивания и приведения типа.

template <class T1, class T2>

Class property

{ typedef T1 (T2::*get)(); // get – синоним для указателя на функцию

// возвращающую T1

typedef void (T2::*set)(T1);

T2 * m_owner;

get m_get;

set m_set;

public:

// Оператор приведения типа. Реализует свойство для чтения.

operator T1()

{ // Здесь может быть проверка "m_owner" и "m_get" на NULL

return (m_owner->*m_get)();

}

// Оператор присваивания. Реализует свойство для записи.

void operator =(T1 data)

{ // Здесь может быть проверка "m_owner" и "m_set" на NULL

(m_owner->*m_set)(data);

}

// Конструктор по умолчанию.

property() :

m_owner(0),

m_get(0),

m_set(0)

{}

// Инициализация объекта property

void init(T2 * const owner, get getmethod, set setmethod)

{ m_owner = owner; // this указатель объекта класса Cls

m_get = getmethod; // указатель на метод get_val()

m_set = setmethod; // указатель на метод put_val()

}

};

Теперь класс, реализующий свойство можно написать так:

Class cls

{ int m_val;

int get_val() //

{ return m_val;

}

void put_val(int val) //

{ m_val = val;

}

public:

property <int, cls> Val;

cls() // Конструктор по умолчанию

{ Val.init(this, get_val, put_val);

}

};

main()

{ cls obj;

// Далее вызывается оператор присваивания переменной-члена

// obj.Val, и, следовательно, функция val.put_val()

obj.Val = 50;

// Далее вызывается оператор приведения типа переменной-члена

// obj.Val, и, следовательно, функция val.get_val()

int z = obj.Val;

}

Как можно видеть, получились настоящие свойства средствами только стандартного синтаксиса С++. Однако, описанный метод не лишен недостатков:

- при каждом обращении к свойству происходит два вызова функции.

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

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

- для каждого свойства необходимо не забыть произвести инициализацию в конструкторе класса-владельца.

*** ВСТАВИТЬ ЗАГОЛОВОК ***

В рассматриваемом ниже примере вычисления факториала производится на этапе компиляции.

#include <iostream>

using namespace std;

template<int n> // описание шаблона структуры Factorial

struct Factorial

{

enum { val = Factorial<n-1>::val * n }; // вычисление факториала

};

template<> // инициализация первого экземпляра

struct Factorial<0> // шаблона структуры Factorial (при n==0)

{

enum { val = 1}; // исходное значение val равное 1

};

int main()

{ int ms[Factorial<4>::val]; // описание массива ms типа int

// размерностью val

cout << Factorial<4>::val << endl; // вывод значения факториала

return 0;

}

В результате выполнения программы получим:

В этом примере при компиляции производится генерация экземпляров шаблона структуры в выражении val = Factorial<n-1>::val * n. Кроме того приводится пример иописаения массива ms размерность val которого также определяется при компиляции.

Пространства имен

При совпадении имен разных элементов в одной области действия часто возникает конфликт имен. Наиболее часто это возникает при использовании различных пакетов библиотек, содержащих, например, одноименные классы.

Пространства имен используются для разделения глобального пространства имен, что позволяет уменьшить количество конфликтов. Синтаксис пространства имен некоторым образом напоминает синтаксис структур и классов. После ключевого слова namespace следует необязательное имя пространства имен, затем описывается пространство имен, заключенное в фигурные скобки.

namespace NAME

{ int a;

doudle b;

char *fun(char *,int);

class CLS

{ . . .

public:

. . .

}

}

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

NAME::b=2;

NAME:: fun(str,NAME:: a);

Внутри пространства имен можно поместить группу объявлений классов, типов и функций. Реализация функций пространства имен должна находиться вне самого пространства имен. Это позволит не только отделить реализацию функций от их объявления, но и избежать загромождения пространства имен. По существу, namespace определяет область видимости.

Использование безымянного пространства имен (отсутствует имя пространства имен) позволяет определить уникальность объявленных в нем идентификаторов с областью видимости в пределах файла.

Контексты пространства имен могут быть вложены.

namespace NAME1

{ int a;

namespace NAME2

{ int a;

int fun1(){return NAME1:: a}; // возвращается значение первого a

int fun2(){return a}; // возвращается значение второго a

}

}

NAME1::NAME2::fun1(); // вызов функции

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

Если элементы пространства имен будут интенсивно использоваться, то можно использовать ключевое слово using для упрощения доступа к ним. Ключевое слово using используется и как директива, и для объявления. Синтаксис слова using определяет, является ли оно директивой или объявлением.

Наши рекомендации