С++ - язык, который изучается постепенно.Образы автоматических объектов
                   Справочники Всё для создания сайта

Ссылки


Home
Бизнес
Справочники
Советы







Материалы книги получены с http://www.itlibitum.ru/

Образы автоматических объектов

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

template <class Type>

class AutoImage {

private:

Type current;

Type image;

bool have_image; // Истина, если образ существует

public:

AutoImage() : have_image(false) {}

AutoImage(const AutoImage<Type>& ai)

: current(ai.current), image(), have_image(false) {}

AutoImage<Type>& operator=(const AutoImage<Type>& ip)

{

if (this != &ip) {

current = ip.current;

have_image = false;

}

return *this;

}

AutoImage<Type>& operator=(const Type& t)

{

current = t;

return *this;

}

operator Type&() { return current; }

void Snapshot()

{

image = current;

have_image = true;

}

void Commit() { have_image = false; }

void Rollback()

{

current = image;

have_image = false;

}

bool HaveImage() { return have_image; }

};

Этот шаблон работает со всеми классами, которые удовлетворяют двум условиям:

1. Тип, используемый в качестве параметра, имеет конструктор без аргументов. Он используется в конструкторе AutoImage для инициализации current и image.

2. Тип, используемый в качестве параметра, допускает присваивание с помощью оператора = по умолчанию, предоставленного компилятором, или перегруженного варианта для данного типа. Используется в функциях Shapshot() и Rollback().

Все встроенные типы (такие как int и double) удовлетворяют этим условиям. Подходят и другие классы, имеющие конструктор без аргументов и рабочий оператор =. Чем дольше я имею дело с C++, тем чаще мне кажется, что нарушение этих требований - проявление злостного непрофессионализма, за которое следует наказывать парой лет каторжного программирования на BASIC. Заодно я бы издал закон о том, чтобы конструкторы копий всегда работали так, как им положено.

Конструктор копий AutoImage следует примеру ImagePtr и ImageStackPtr - он использует

конструктор без аргументов для создания фиктивного объекта image и присваивает have_image значение false. Оператор = делает то же самое, однако в нем не удается найти удобный способ уничтожить объект переменной image. Мы выбираем меньшее из двух зол - объект остается без изменений и попросту игнорируется, поскольку переменная have_image равна false. Если вас это не устраивает и вы действительно хотите оставить объект image неинициализированным до тех пор, пока в нем не появится настоящий образ, и уничтожить его после присвоения false переменной have_image, имеются два возможных решения:

1. Изменить тип image с Type на Type* и выделять для него память оператором new. Это

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

2. Воспользоваться идиомой «виртуальных конструкторов» из главы 13. Не вдаваясь в

подробности, скажу, что это позволит вам объявить image чем-то приятным для глаза -

например, unsigned char image(sizeof Type) - нежели вызывать конструктор и деструктор Type вручную. Компиляторы С++ недолюбливают подобные фокусы, поэтому, прежде чем пускаться на авантюры, внимательно прочитайте главу 13.

Если AutoImage будет использоваться только для структур или классов, добавьте оператор ->:

Type* operator->() { return &current; } Обратите внимание: в отличие от предыдущих версий -> этот оператор не может быть константной функцией, поскольку current находится внутри *this и мы не можем гарантировать, что -> не будет использоваться для обращений к неконстантным функциям current.

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

class Foo {

private:

AutoImage<int> some_integer;

AutoImage<Bar> bar;

public:

void Rollback()

{

some_integer.Rollback();

bar.Rollback();

}

void Commit()

{

some_integer.Commit();

bar.Commit();

}

void Snapshot()

{

some_integer.Snapshot();

bar.Snapshot();

}

int ProvideInt() const { return some_integer; }

void ChanheInt(int new_value)

{

if (!some_integer.HaveImage())

some_integer.Snapshot();

int&(some_integer) = new_value;

}

const Bar& ProvideBar() const { return bar; }

Bar& UpdateBar()

{

if (!bar.HaveImage())

bar.Shapshot();

return Bar&(bar);

}

};

Предполагается, что Bar соответствует необходимым условиям. Последние четыре функции перед тем, как обновлять переменную, создают «моментальный снимок» объекта. Для int получение копии по определению является константным по отношению к копируемой переменной. При вызове функции, изменяющей значение переменной, настает время делать снимок. Для работы с другой переменной, bar, предоставляется как константная, так и неконстантная функция. Конечно, хотелось бы просто перегрузить функцию ProvideBar(), чтобы одна перегруженная версия возвращала const Bar&, а другая - неконстантный Bar&, но тогда их сигнатуры будут совпадать. Помните: две функции не могут иметь одинаковые имена и аргументы и отличаться только типом возвращаемого значения. Я никогда не понимал этого ограничения С++, которое запрещает создавать константную и

неконстантнуюверсию оператора ->:

const Type* operator->() const; // Снимок не создается

Type* operator->() const; // Создает снимок

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

Раз уж речь зашла об ограничениях С++, упомяну еще об одном. Взгляните на приведенный выше код класса Foo. Работа некоторых его функций сводится к вызову одной и той же функции для всех переменных класса и в более общем случае - базовых классов. Скажем, Foo::Commit() просто вызывает Commit() для всех переменных. Весь повторяющийся код приходится писать вручную; в языке сильно не хватает макросредств, которые бы позволяли сказать: «Вызвать функцию Commit() для каждой переменной класса». Компилятор знает, как составить список такого рода (и использует его в конструкторах), но вам ни за что не скажет.


Назад    Содержание    Далее    

Home  Создание сайтов  Учебник по записи CD  Справочник Web дизайнера Самоучитель IE PHP и MySQL Компьютерные сети С++ E-mail me

Copyright 2007. Климов Александр. All Right Reserved.
Hosted by uCoz