Анимация
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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105

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

template <class т> class SmartPtr {

public:

bool operatorIC) const Допускает оператор "if C!sp) ..." {

return pointee = 0;

inline friend bool operator==Cconst SmartPtr& Ihs, const T* rhs)

return lhs.pointee == rhs;

inline friend bool operator==Cconst т* Ihs, const SmartPtr* rhs)

return Ihs == rhs.pointee ;

inline friend bool operator!=Cconst SmartPtr& Ihs, const T* rhs)

return lhs.pointee != rhs;

inline friend bool operator!=Cconst т* Ihs, const SmartPtr& rhs)

return Ihs != rhs.pointee ; >; "" .

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

Однако проблема рещена не полностью. Если в профамме предусмафивается автоматическое преобразование указателей, остается риск, связанный с неоднозначностью такой операции. Допустим, у нас есть класс Base и класс Derived, производный от него. Тогда в следующем коде будет содержаться неоднозначность.

SmartPtr<Base> sp; Derived* р;

if (sp == p) {} Ошибка! Существует неоднозначность между

проверкой CBase*)sp == CBase*)p и

функцией operator==Csp, CBase*)p)

Действительно, разработка интеллектуальных указателей - занятие не ддя слабонервных.

Но и это еще не все. Кроме определения операторов == и I =, мы можем создать их шаблонные версии.

template <class т> class SmartPtr



public:

как и раньше ... template <c1ass и>

inline friend bool operator==Cconst smartPtr& Ihs, const u* rhs)

return lhs.pointee == rhs; emplate <class u>

nline friend bool operator==Cconst u* Ihs, const SmartPtr& rhs)

return Ihs == rhs.pointee ;

.. аналогично определенный оператор != ...

Шаблонные операторы выполняют сравнения с любым типом указателей, устраняя неоднозначность.

Если все так хорошо, зачем сохранять обычные, не шаблонные операторы, рабо-таюшие с типами объектов, на которые ссылаются указатели? Эти операторы никогда не пройдут процедуру сопоставления, потому что шаблоны сопоставляют любые типы указателей, в том числе и типы объектов, на которые ссылаются указатели.

Однако "никогда не говори никогда". При выполнении проверки if (sp == 0) компилятор пытается выполнить следующие сопоставления.

• Шаблонные операторы (templated operators). Они не проходят проверку, поскольку нуль не является типом указателей. Литеральный нуль можно неявно преобразовать в тип указателей, однако шаблонное сопоставление не предусматривает преобразований типов.

• Нешаблонные операторы (nontemplated operators). Исключив шаблонные операторы, компилятор переходит к проверке нешаблонных. Один из этих операторов оказывается подходящим после выполнения неявного преобразования литерального нуля в тип указателя. Если бы нешаблонных операторов не было, проверка завершилась бы сообщением об ошибке.

Итак, и шаблонные, и нешаблонные операторы одинаково необходимы.

Посмотрим теперь, что произойдет при попытке сравнения двух классов SmartPtr, конкретизированных разными типами.

SmartPtr<Apple> spl; SmartPtr<Orange> sp2; if (spl == sp2)

Компилятор споткнется на этом сравнении из-за неоднозначности: в каждой из этих двух конкретизации определен оператор ==, и компилятор не знает, который из них выбрать. Мы можем избежать этой проблемы, определив "ликвидатор неоднозначностей".

template <class т> class SmartPtr {

public:

ликвидатор неоднозначностей template <class u>

bool operator==Cconst SmartPtr<u>& rhs) const 198 Часть II. Компоненты



return pointee == rhs.pointee ;

Аналогично для оператора !=

Новый оператор является членом класса, предназначенным исключительно для сравнения объектов классов SmartPtr<.. .>. Прелесть этого ликвидатора неоднозначностей заключается в том, что он сравнивает интеллектуальные указатели, как обычные указатели. Если сравнить два интеллектуальных указателя на объекты классов Ар-р1е и Orange, код будет, по существу, эквивалентным сравнению обычных указателей на классы Apple и Orange. Если это сравнение имеет смысл, код компилируется, в противном случае выдается сообщение об ошибке.

SmartPtr<Apple> spl; SmartPtr<Orange> sp2;

if (spl == sp2) Семантически эквивалентно сравнению spl.pointee == sp2.pointee

Остался один неприятный артефакт - непосредственная проверка if (sp). Вот это уже действительно интересно! Оператор if применяется только к выражениям арифметического типа или к указателям. Следовательно, чтобы скомпилировать оператор if (sp), нужно определить автоматическое преобразование интеллектуального указателя в арифметический тип или в обычный указатель.

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

Если нужно преобразовать интеллектуальный указатель в тип, на который он ссылается (см. предьшущий раздел), у нас есть выбор: либо рисковать, допуская возможность непредвиденного вызова оператора delete, или воздержаться от проверки if (sp). Что выбрать: неудобство или опасность? Следует предпочесть безопасность, поэтому проверку if (sp) выполнять не следует. Вместо этого можно выбрать проверку ifCsp != 0) или более вычурное выражение ifC! !sp).

Если вы не хотите предусматривать преобразование интеллектуального указателя в тип, на который он ссылается, можно прибегнуть к интересному трюку, который позволит все же выполнить проверку if (sp). Внутри шаблонного класса SmartPtr нужно определить внутренний класс Tester и преобразование в тип Tester*, как показано ниже.

template <class т> class SmartPtr {

class Tester {

void operator deleteCvoid*);

public:

operator Tester*C) const {

if C!pointee ) return 0; static Tester test; return &test;



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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105