Анимация
JavaScript
|
Главная Библионтека public: operator T*C) Приведение к типу т*, определенное пользователем return pointee ; Однако это еще не все. Преобразования типов, определенные пользователем, имеют весьма интересную историю. В 1980-х годах, когда они впервые появились в языке G++, большинство программистов считали их офомным достижением. Они позволяли разрабатывать единообразные системы типов, повышать выразительность семантики и определять новые типы, отличаюшиеся от всфоенных. Однако впоследствии оказалось, что преобразования типов, определенные пользователем, неудобны и потенциально опасны. Они могут стать опасными особенно при обработке внутренних данных (Meyers, 1998а, Item 29). Именно это происходит с типом Т* в приведенном выше коде. Вот почему следует хорошенько подумать, прежде чем допускать автоматическое преобразование интеллектуальных указателей в вашей программе. Одна из потенциальных опасностей происходит от неконфолируемого доступа пользователя к обычному указателю, который скрыт внутри интеллектуального. Передача обычного указателя во внешнюю среду нарушает внутреннюю работу интеллектуального указателя. Вырвавшись из своей оболочки, обычный указатель может легко стать уфозой для нормального функционирования профаммы, как это было до появления интеллектуальных указателей. Другая опасность исходит от непредвиденного выполнения преобразований, определенных пользователем, даже когда они совершенно не нужны. Рассмофим следую-ший пример. SmartPtr<Something> sp; грубая семантическая ошибка. Однако компилятор ее не распознает delete sp; Компилятор сопоставляет оператор delete с преобразованием к типу т*, определенным пользователем. Во время вьтолнения профаммы вызывается оператор т*, и к результату его работы применяется оператор del ete. Очевидно, это совершенно не то, что требуется от интеллектуального указателя, поскольку предполагается, что он сам управляет правами владения. Лишний и непреднамеренный вызов оператора delete нарушает всю тшательно отлаженную систему управления правами владения, скрытую внутри интеллектуального указателя. Есть несколько способов преодотвратить компиляцию вызова оператора delete. Некоторые из них очень остроумны (Meyers, 1996). Эффективнее и удобнее всего сделать вызов оператора delete внутренне неоднозначным (ambiguous). Этого можно достичь, предусмотрев два приведения к типам, реагирующих на вызов оператора delete. Один из типов - это сам тип т*, а вторым может быть тип void*. template <c1ass Т> class SmartPtr { public: operator T*C) Приведение к типу Т*, определенное пользователем return poiintee ; operator void*C) добавляется приведение к типу void* { return pointee ; Применение оператора delete к интеллектуальному указателю неоднозначно. Компилятор не сможет решить, какое из преобразований следует выполнить. Именно этого мы и добивались. Не забывайте, что блокировка оператора del ete - это только часть проблемы. Остается решить, применять ли автоматическое преобразование интеллектуального указателя в простой указатель. Разрешить - слишком опасно, запретить - очень легко. Окончательная реализация класса SmartPtr предоставляет профаммисту право выбора. Однако запрет неявного преобразования не означает полного исключения доступа к обычному указателю. Иногда такой доступ просто необходим. Следовательно, все интеллектуальные указатели должны обеспечивать явный доступ к скрытому внуфи них обычному указателю с помошью вызова функции. void Fun(Something* р) SmartPtr<Something> sp; FunCGetimpl(sp)); явное преобразование всегда разрешено Вопрос заключается не в том, можете ли вы получить доступ к обычному указателю, а в том, насколько это легко. Может показаться, что разница между явным и неявным преобразованиями невелика, однако она очень важна. Неявное преобразование происходит независимо от профаммиста, он может даже ничего не знать о нем. Явное преобразование, как, например, вызов функции Getlmpl, выполняется осознанно, управляется профаммистом и не скрывается от пользователей программы. Неявное преобразование интеллектуального указателя в простой указатель желательно, но иногда опасно. Класс SmartPtr предоставляет профаммисту право самому решать, допускать его или нет. По умолчанию предлагается безопасный вариант - неявное преобразование запрешается. Явный доступ к обычному указателю всегда открыт через функцию Getlmpl. 7.8. Равенство и неравенство Любой фюк в языке С++, например, описанный выше (внуфенняя неоднозначность), создает новый контекст, который в свою очередь может иметь неожиданные последствия. Рассмотрим проверку интеллектуальных указателей на равенство и неравенство. Интеллектуальный указатель должен поддерживать такой же синтаксис проверки, что и обычные указатели. Профаммист ожидает, что приведенные ниже проверки будут скомпилированы и выполнены. SmartPtr<Something> spl, sp2; Something* p; if Cspl) Проверка 1: прямая проверка ненулевого указателя Глава 7. Интеллектуальные указатели 195 if (!spl) проверка 2: прямая проверка нулевого указателя if (spl == 0) проверка 3: явная проверка нулевого указателя if (spl == sp2) проверка 4: сравнение двух интеллектуальных указателей if (spl == р) Проверка 5: сравнение с обычным указателем В этом фрагменте проиллюстрированы не все возможные проверки. Реализовав проверку равенства, мы сможем легко проверять и неравенства. К сожалению, между проблемой проверки равенства и проблемой предотвращения компиляции оператора delete существует скрытая связь. Если в программе определено только одно преобразование, больщинство проверок (за исключением проверки 4) проходит успещно, и программа выполняется в соответствии с ожиданиями профаммиста. Единственный недостаток - возможность непредвиденного применения оператора delete к интеллектуальному указателю. Если в профамме определены два преобразования (внутренняя неоднозначность), неверные вызовы оператора delete распознаются, но ни одна из указанных выше проверок больше не компилируется - они также становятся неоднозначными. Дополнительное преобразование интеллектуального указателя в булевскую переменную является полезным, но небезопасным. Рассмотрим следующее определение интеллектуального указателя. template <class т> class SmartPtr { public: operator boolO const { return pointee != 0; Четыре указанные выше проверки успешно компилируются, но при этом выполняются следующие бессмысленные операции. SmartPtr<Apple>- spl; SmartPtr<Orange> sp2; яблоко и апельсин - не одно и то же if (spl == sp2) Оба указателя преобразовываются в булевскую переменную, а результаты сравниваются if (spl != sp2) To же самое bool b = spl; Допустимая операция if (spl * 5 == 200) Ой! интеллектуальный указатель ведет себя как целое число! Как видим, мы можем получить либо все, либо ничего. Добавив преобразование интеллектуального указателя в булевскую переменную, мы допустили ситуации, когда объект класса SmartPtr ведет себя неправильно. Таким образом, определение оператора bool для интеллектуального указателя оказалось неразумным решением. 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 |