77
};
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
class Ptr {
private:
Type*
pointer;
public:
Ptr() : pointer(NULL) {}
Ptr(Type* p) : pointer(p) {}
operator Type*() { return pointer; }
ptr_diff operator-(Ptr p) { return pointer – p.pointer; }
ptr_diff operator-(void* v) { return ((void*)pointer) – v; }
Ptr operator-(long index) { return Ptr(pointer – index); }
Ptr operator-=(long index) { pointer -= index; return *this; }
Ptr operator+(long index) { return Ptr(pointer + index); }
Ptr operator+=(long index) { pointer += index; return *this; }
};
Важно понимать, чем
ptr_diff
отличается от целого индекса. При вычитании одного адреса из
другого результатом является смещение, как правило, выраженное в байтах. В случае прибавления
целого к указателю адрес изменяется на размер объекта, умноженный на целое. Помните: в C++, как и
в С, указатель ссылается не на один объект, а на теоретический массив объектов. Индексы в описанных
выше перегруженных операторах представляют собой индексы этого теоретического массива, а не
количества байт.
После всего сказанного я бы не советовал пускаться на эти хлопоты для умных указателей — не из-за
лени, а потому, что в этом случае вы обрекаете себя на решения, которые не всегда желательны. Если
дать пользователю возможность складывать и вычитать указатели, вам неизбежно придется
поддерживать идею, что указатель всегда индексирует воображаемый массив. Как выяснится в
следующих главах, многие варианты применения умных указателей не должны или даже не могут
правильно работать с парадигмой массива.
78
Во что обходится умный указатель?
Объект класса, не содержащего виртуальных функций, занимает столько места, сколько необходимо
для хранения всех его переменных. В рассмотренных выше умных указателях используется всего одна
переменная —
*
-указатель; то есть размер умного указателя в точности совпадает с размером
встроенного указателя. Хороший компилятор C++ должен специальным образом обрабатывать
тривиальные подставляемые функции, в том числе и находящиеся в шаблоне умного указателя.
template
class Ptr {
private:
Type*
pointer;
public:
Ptr() : pointer(NULL) {}
Ptr(Type* p) : pointer(p) {}
operator Type*() { return pointer; }
Type* operator->() { return pointer; }
};
В частности, использование оператора
->
из этого шаблона не должно требовать никаких
дополнительных вычислений по сравнению со встроенными указателями. Как всегда, обращайте
особое внимание на слова хороший и должно. В хорошей реализации описанные выше умные
указатели не требуют никаких дополнительных расходов. По крайней мере, хуже пока не стало.
Применения
Умные указатели — существа на редкость полезные, и мы проведем немало времени, изучая их
применение на практике. Для простых умных указателей, рассматриваемых в этой главе, находятся
столь же простые, но мощные применения.
Разыменование значения NULL
Рассмотрим одну из вариаций на тему умных указателей:
template
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
выводится
сообщение об ошибке, после чего создается фиктивный объект и умный указатель переводится на него,
чтобы программа могла хромать дальше.
Do'stlaringiz bilan baham: |