Анимация
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

SP() : pointer(NULL) {} SP(Type* p) : pointer(p) {} operator Type*() { return pointer; } Type* operator->() { return pointer; }

void f(Foo*); Ptr<Foo> pf(new Foo);

f(pf); Работает благодаря функции operator Type*()

pf->MemberOfFoo(); Работает благодаря функции operator->()

Этот шаблон подойдет для любого класса, не только для класса Foo. Перед вами - одна из базовых форм умных указателей. Она используется достаточно широко и даже может преобразовать указатель на производный класс к указателю на базовый класс при условии, что вы пользуетесь хорошим компилятором.

Хороший компилятор C++ правильно обрабатывает такие ситуации, руководствуясь следующей логикой:

1. Существует ли конструктор P<Foo>, который получает Р<Ваr>? Нет. Продолжаем поиски.

2. Существует ли в Р<Ваr> операторная функция operator P<Foo>()? Нет. Ищем дальше.

3. Существует ли пользовательское преобразование от Р<Ваr> к типу, который подходит под сигнатуру какого-либо конструктора P<Foo>? Да! Операторная функция operator Bar*() превращает Р<Ваr> в Bar*, который может быть преобразован компилятором в Foo*. Фактически выражение вычисляется как Ptr<Foo>pf2(Foo*(pb.operator Bar*())), где преобразование Bar* в Foo* выполняется так же, как для любого другого встроенного указателя.

Как я уже говорил, все должно работать именно так, но учтите - некоторые компиляторы обрабатывают эту ситуацию неправильно. Даже в хороших компиляторах результат вложения подставляемой (inline) операторной функции operator Bar*() во встроенный P<Foo>(Foo*) может быть совсем не тем, на который вы рассчитывали; многие компиляторы создают вынесенные (а следовательно, менее эффективные) копии встроенных функций классов вместо того, чтобы генерировать вложенный код подставляемой функции. Мораль: такой шаблон должен делать то, что вы хотите, но у компилятора на этот счет может быть другое мнение.

Иерархия умных указателей

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

class PVoid { Заменяет void*

protected:

void* addr; public:

PVoid() : addr(NULL) {}

PVoid(void* a) : addr(a) {}

operator void*() { return addr; }

class Foo : public PVoid { public:

PFoo() : PVoid() {}

PFoo(Foo* p) : PVoid(p) {}

operator Foo*() { return (Foo*)addr; }

Foo* operator->() { return (Foo*)addr; }



class Pbar : public PFoo { public:

PBar() : PFoo() {}

PBar(Bar* p) : PFoo(p) {}

operator Bar*() { return (Bar*)addr; }

Bar* operator->() { return (Bar*)addr; }

pBar pb(new Bar);

pFoo pf(pb); Работает, потому что PBar является производным от PFoo

pf->MemberOfFoo(); Работает благодаря PFoo::operator->

Этот вариант будет работать, если вас не огорчают многочисленные копирования/вставки текста и (в зависимости от компилятора) предупреждения о том, что РВаr::operator->() скрывает PFoo::operator->(). Конечно, такое решение не настолько элегантно, как встроенные типы указателей шаблона Ptr.

Арифметические операции с указателями

Ниже показан пример арифметических операторов, обеспечивающих работу операций сложения/вычитания для умных указателей. Для полной, абсолютно совместимой реализации к ним следует добавить операторы ++ и -- .

template <c1ass Type> class Ptr { private:

Type* pointer; public:

Ptr() : pointer(NULL) {}

Ptr(Type* p) : pointer(p) {}

operator Type*() { return pointer; }

ptr diff operator-(Ptr<Type> p) { return pointer - p.pointer; } ptr diff operator-(void* v) { return ((void*)pointer) - v; } Ptr<Type> operator-(1ong index) { return Ptr<Type>(pointer - index); } Ptr<Type> operator-=(1ong index) { pointer -= index; return *this; } Ptr<Type> operator+(1ong index) { return Ptr<Type>(pointer + index); } Ptr<Type> operator+=(1ong index) { pointer += index; return *this; }

Важно понимать, чем ptr diff отличается от целого индекса. При вычитании одного адреса из другого результатом является смещение, как правило, выраженное в байтах. В случае прибавления целого к указателю адрес изменяется на размер объекта, умноженный на целое. Помните: в C++, как и в С, указатель ссылается не на один объект, а на теоретический массив объектов. Индексы в описанных выше перегруженных операторах представляют собой индексы этого теоретического массива, а не количества байт.

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



Во что обходится умный указатель?

Объект класса, не содержащего виртуальных функций, занимает столько места, сколько необходимо для хранения всех его переменных. В рассмотренных выше умных указателях используется всего одна переменная - *-указатель; то есть размер умного указателя в точности совпадает с размером встроенного указателя. Хороший компилятор C++ должен специальным образом обрабатывать тривиальные подставляемые функции, в том числе и находящиеся в шаблоне умного указателя.

template <c1ass Type> class Ptr { private:

Type* pointer; public:

Ptr() : pointer(NULL) {}

Ptr(Type* p) : pointer(p) {}

operator Type*() { return pointer; }

Type* operator->() { return pointer; }

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

Применения

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

Разыменование значения NULL

Рассмотрим одну из вариаций на тему умных указателей: template <c1ass Type> class SPN { private:

Type* pointer; public:

SPN() : pointer(NULL) {}

SPN(Type* p) : pointer(p) {}

operator Type*() { return pointer; }

Type* operator->()

if (pointer == NULL) {

cerr << "Dereferencing NULL!" << endl; pointer = new Type;

return pointer;

При попытке вызвать оператор -> для указателя pointer, равного NULL, в поток stderr выводится сообщение об ошибке, после чего создается фиктивный объект и умный указатель переводится на него, чтобы программа могла хромать дальше.



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