143
Следующий класс демонстрирует возможное использование этого шаблона. Вмещающему объекту
Foo
незачем создавать свой образ, как в предыдущих указателях на объекты, поскольку все его переменные
способны поддерживать свои образы по отдельности.
class Foo {
private:
AutoImage
some_integer;
AutoImage
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;
// Создает снимок
144
Конечно, это намного упростило бы жизнь, но назвать эти загадочные ограничения бесполезными
нельзя — они дают знатокам С++ хорошую тему для разговоров на семинарах с коктейлями.
Раз уж речь зашла об ограничениях С++, упомяну еще об одном. Взгляните на приведенный выше код
класса
Foo
. Работа некоторых его функций сводится к вызову одной и той же функции для всех
переменных класса и в более общем случае — базовых классов. Скажем,
Foo::Commit()
просто
вызывает
Commit()
для всех переменных. Весь повторяющийся код приходится писать вручную; в
языке сильно не хватает макросредств, которые бы позволяли сказать: «Вызвать функцию
Commit()
для каждой переменной класса». Компилятор знает, как составить список такого рода (и использует его
в конструкторах), но вам ни за что не скажет.
Образы указателей
У шабона
AutoImage
есть одно довольно занятное применение — им можно воспользоваться для
создания образов
*
-указателя. В некоторых ситуациях не хочется создавать лишние копии
указываемого объекта только чтобы следить за тем, на что ссылался указатель в прошлой жизни.
Собственно, дело обстоит так каждый раз, когда указатель не является ведущим. Указатель также
помогает следить за объектами, которые были созданы или уничтожены в процессе транзакции.
AutoImage f;
Теперь вы можете восстановить состояние указателя
f
в начале транзакции, в том числе и
NULL
. Тем не
менее, существует веский довод в пользу создания специализированного шаблона для
*
-указателей —
необходимость перегрузки оператора
->
, чтобы указатель образов можно было использовать в левой
части выражений (что-нибудь типа
ptr->MemberOfPointer();
). Для
*
-указателей
AutoImage
похож
на глупые указатели, с которыми мы рассправились в начале главы 5. Следующий шаблон больше
напоминает обычные умные (но не ведущие!) указатели.
template
class PtrImage {
private:
Type*
current;
Type*
image;
bool
have_image;
//
Истина, если образ существует
public:
PtrImage() : current(NULL), image(NULL), have_image(false) {}
PtrImage(const
PtrImage&
pi)
: current(pi.current), image(NULL), have_image(false) {}
PtrImage& operator=(const PtrImage& pi)
{
if (this != &pi)
current
=
pi.current;
return
*this;
}
PtrImage&
operator=(Type*
t)
{ current = t; return *this; }
operator Type*() { return current; }
Type* operator->() const { return current; }
bool operator!() { return current == NULL; }
void
Snapshot()
{
image
=
current;
have_image
=
true;
}
void Commit() { image = NULL; have_image = false; }
Do'stlaringiz bilan baham: |