221
содержимое (то есть создать итерацию для набора умных указателей). В классах все эти
*
-указатели
заменяются дескрипторами или ссылками на умные указатели, поскольку сами указатели должны
находиться в отдельном пространстве. В следующем фрагменте программы
P
и
H
представляют собой
стандартные указатели и дескрипторы соответственно, за исключением того, что
P
сохраняет
экземпляры указателей в специальном пространстве памяти. Эта методика хорошо подойдет и для
невидимых указателей, если для их сохранения в надежном, хорошо известном месте будет
использована одна из методик нестандартных пространств памяти. Указатель
P
обычно является
ведущим, но это не обязательно.
template
class P { //
Указатель
private:
Type*
pointee;
public:
void*
operator
new(size_t); //
Использует специальное
// пространство памяти
void operator delete(void*);
// Использует специальное
// пространство памяти
//
Все для умных указателей
};
template
class H { //
Дескриптор
private:
P*
ptr;
public:
//
Все для дескрипторов
};
class Foo {
private:
P&
bar;
//
Ссылка на умный указатель на Bar
//
ИЛИ
H&
bar;
//
Дескриптор Bar
};
В первом варианте мы храним ссылку на умный указатель, причем сам указатель, вероятно, хранится
где-то в другом месте. Во втором варианте мы используем идиому дескриптора — умного указателя на
умный указатель. Сам дескриптор находится в объекте, но указатель, на который он ссылается, — в
специальном пространстве указателей, используемом операторами
new
и
delete
класса
P
. Если
пространство указателей будет реализовано толково, все указатели можно будет перебирать прямо из
него. В это случае задача перемещения объекта несколько упрощается (хотя и не становится простой),
поскольку все указатели на него можно найти в пространстве указателей. Полная реализация этой
методики приведена ниже в этой главе.
Скрытые коллекции указателей
Другое возможное решение — поддержать скрытые коллекции умных указателей.
template
class P {
private:
static
P*
head;
//
Начало списка MP
static
P*
tail;
//
Конец списка
P*
next;
//
Следующий элемент списка
P*
previous;
//
Предыдущий элемент списка
Type*
pointee;
222
public:
P();
//
Занести ‘this’ в список
P(const P & p);
// Занести ‘this’ в список
~P(); //
Удалить ‘this’ из списка
P& operator=(const P& p);
// Не изменяя список,
// скопировать p.pointee
//
Все для умных указателей
};
Вам придется соблюдать осторожность при выполнении операций со списком в конструкторе копий и
операторе
=
, но во всем остальном реализация достаточно тривиальная. Используя этот шаблон, класс
обходится без хранения ссылок на умные указатели; он хранит их непосредственно.
class Foo {
private:
P
bar;
//
Указатель автоматически заносится в скрытую коллекцию
};
При конструировании
Foo
вызывается соответствующий конструктор
P
, который автоматически
заносит
bar
в скрытую коллекцию. При уничтожении
Foo
вызывает деструктор
P
, который удаляет
bar
из коллекции. Разумеется, вместо двусвязного списка можно воспользоваться другими
структурами данных. Кроме того, как вы вскоре убедитесь, для всех этих специализированных
указателей стоит создать общий базовый класс и сохранить их все в одной коллекции. В приведенном
выше фрагменте для каждого типа указателя создается отдельная коллекция.
Анализ экземпляров
Более радикальный подход — использовать самые обычные указатели и предусмотреть средства для
перебора всех указателей, внедренных в экземпляр.
class Foo {
private:
Bar*
bar;
};
В данной схеме это разрешается, при условии, что какая-то очень сложная архитектура сможет
определить
Bar*
по имеющемуся
Foo
. Чтобы реализовать ее, нам понадобится код, который знает
структуру каждого экземпляра (а точнее, умеет находить переменные класса, которые являются
адресами или могут содержать адреса посредством рекурсии), и возможность точно определить тип
указываемого объекта. Например, успешно найденный
bar
не поможет, если вы не знаете, с чем
имеете дело — с настоящим Bar или каким-то классом, производным от
Bar
. В производном классе
могут появиться дополнительные указатели, отсутствующие в
Bar
.
Мы еще вернемся к этому решению, но если бы это была реклама автомобиля, то в нижней части
экрана вы бы увидели предупреждение: «Профессиональный каскадер на закрытом треке. Не
пытайтесь повторить в домашних условиях». Тем не менее, во многих ситуациях простые решения не
работают, поэтому в следующей главе мы с головой нырнем в эту схему.
Стековые переменные
Конечно, не все указатели являются членами объектов. Некоторые из них — обычные переменные,
находящиеся в стеке. Разумеется, решение со специальными пространствами для стековых переменных
не подойдет, если только они не являются дескрипторами или ссылками на умные указатели,
хранящиеся в другом месте. Скрытые коллекции с тем же успехом будут работать для указателей,
хранящихся в стеке или куче — при условии, что вы организуете обработку исключений, которая будет
правильно раскручивать стек. Особого обращения требует лишь одна переменная
this
, значение
которой задается компилятором, а не вашим кодом, работающим с умными указателями. Стековые
переменные значительно усложняют решение с анализом экземпляров, поскольку вам также придется
разрабатывать отдельную схему обнаружения или хотя бы предотвращения коллизий.
Do'stlaringiz bilan baham: |