Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 [ 24 ] 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82

Ведущие указатели и дескрипторы


После совсем непродолжительного знакомства с умными указателями мы успели наткнуться на целый ряд фундаментальных проблем. Многие из них связаны с тем фактом, что на один объект может ссылаться любое количество умных указателей. Как в этом случае узнать, когда можно удалить объект? Кто ведет статистику использования объекта и обращается к ней при необходимости (если вам вдруг понадобится такая возможность)? Кто создает объект? Что означает присваивание одного умного указателя другому? Заиграет ли наконец в этом сезоне наша любимая команда или она снова разобьет наши сердца? Ниже вы найдете ответы на эти и многие другие интригующие вопросы.

Семантика ведущих указателей

При работе с умными указателями имеется один важный частный случай - когда два умных указателя не должны одновременно ссылаться на один объект. Между указателем и объектом, на который он ссылается, существует однозначное соответствие (за исключением особого случая умных указателей, ссылающихся на NULL). Если в программном дизайне действует такое ограничение, говорят, что реализуется семантика ведущих указателей (master pointers).

Конечно, можно просто объявить через местную газету, что указатели должны использоваться таким и только таким образом. А можно защитить ваших пользователей от самих себя и подкрепить семантические правила языковыми средствами C++. Если вы мудро выберете второй вариант, придется учесть следующее:

1 . Указываемые объекты должны создаваться указателями в конструкторах.

2. Деструктор указателя должен удалять указываемый объект.

3. Конструктор копий должен создавать точную копию указываемого объекта.

4. Оператор присваивания operator= должен удалять текущий указываемый объект, находящийся слева от него, и заменять его копией указываемого объекта справа.

Кроме того, было бы разумно сделать еще две вещи:

5. Ограничить доступ к конструкторам класса указываемого объекта.

6. Создавать указатели с помощью производящих функций (factory functions).

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

template <c1ass Type> class MP { private:

Type* t; public:



MP(); Создает указываемый объект

MP(const MP<Type>&); Копирует указываемый объект ~MP(); Удаляет указываемый объект

MP<Type>& operator=(const MP<Type>&); Удаляет левосторонний объект,

копирует правосторонний

Type* operator->() const;

Конструирование

Предположим, вы разрешили пользователям создавать собственные указываемые объекты и передавать их ведущим указателям во время конструирования.

template <c1ass Type>

class MP {

private:

Type* t; public:

MP() : t(NULL) {}

MP(Type* pointer) : t(pointer) {} ~MP() { delete t; } И т.д.

Foo* foo = new Foo;

MP<Foo> mpl(foo);

MP<Foo> mp2(foo); Облом!

Насколько проще была бы жизнь без таких пользователей! Когда mpl удаляется, пропадает и foo. В итоге mp2 начинает указывать неизвестно куда. «Зачем кому-нибудь потребуется вытворять такое?» - спросите вы. Как говорилось в одном рекламном ролике: «Зачем спрашивать «Зачем»?» Если вы оставите такую дыру, можете не сомневаться: кто-нибудь когда-нибудь изобретет дьявольский план, использует ее и обвинит во всех смертных грехах вашу программу.

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

class Foo {

friend class MP<Foo>;

protected:

Foo(); Теперь доступ к конструктору имеет только MP<Foo> public:

Оставшаяся часть интерфейса

template <c1ass Type>

class MP {

private:

Type* t; public:

MP() : t(new Type) {}

И т.д.

Ага, уже лучше. При создании указателя его конструктор также конструирует и указываемый объект. Объявляя указатель другом, мы можем сделать конструкторы Foo закрытыми или защищенными и



сохранить доступ к ним из конструкторов указателя. Теперь клиент никак не сможет добраться до Foo, кроме как через MP<Foo>. Мы еще неоднократно вернемся к вопросу о том, как, когда и где создавать указываемые объекты, а пока давайте немного отвлечемся.

Если конструкторы Foo вызываются с аргументами, существуют две альтернативы:

1 . Вместо того чтобы пользоваться универсальным шаблоном ведущего указателя, создайте для Foo нестандартный класс ведущего указателя MPFoo. Для каждого конструктора Foo создайте конструктор MPFoo с точно такой же сигнатурой и передайте его аргументы конструктору Foo.

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

Второй вариант выглядит так: class Foo { friend class MP<Foo>; protected:

Foo(); Единственный конструктор public:

Initia1ized(int, char*);

Оставшаяся часть интерфейса

MP<Foo> mpf;

mpf->Initia1ize(17, "Hello"); Завершить конструирование

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

Уничтожение

Нет ничего проще: в деструкторе ведущего указателя удаляется и указываемый объект. template <c1ass Type> class MP { private:

Type* t; public:

~MP() { delete t; }

Копирование

Ой! Опять эти проклятые пользователи...

MP<Foo> mpfl; Создает Foo, на который ссылается указатель

MP<Foo> mpf2 = mpfl; Неудача!

Пусть знак равенства не вводит вас в заблуждение - здесь происходит конструирование, и эта строка эквивалентна строке MP<Foo> mpf2(mpf1);. Если не перегрузить конструктор копий и разрешить компилятору C++ внести свою лепту, мы получим два ведущих указателя, которые ссылаются на один и тот же объект Foo. По умолчанию конструктор копий, генерируемый компилятором, радостно копирует содержащийся в переменной адрес из старого указателя в новый. Проблема решается относительно просто.

template <c1ass Type> class MP {



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 [ 24 ] 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82