|
Программирование >> Оптимизация возвращаемого значения
Глава 2. Операторы к перегружаемым операторам следует относиться с уважением. Они позволяют применять для типов, определяемых пользователем, такой же синтаксис, как и для встроенных типов, а также обеспечивают неслыханные перспективы благодаря функциям, стоящгш за этими операторами. Но возможность заставить такие символы, как + или ==, делать все что угодно, означает также, что из-за перегружаемых операторов программы могут оказаться совершенно непонятными. Тем не менее, есть много искусных программистов на С++, которые знают, как использовать мощь перегружаемых операторов, не превратив программу в черный ящик. Менее искусным, к сожалению, легко совершить ошибку. Конструкторы с единственным аргументом и операторы неявного преобразования типа могут доставить особенно много хлопот, поскольку их вызовы не всегда имеют соответствия в исходных текстах программ. Это ведет к появлению программ, поведение которых понять очень трудно. Другая проблема возникает при перегрузке таких операторов, как && и II, потому что переход от встроенных операторов к функциям, написанным пользователем, приводит к незначительным изменениям в семантике, и эти изменения легко проглядеть. Наконец, множество операторов соотносится друг с другом по стандартным правилам, а из-за перегруженных операторов общепринятые соотношения иногда нарушаются. В изложенных далее правилах я попытался объяснить, когда и как используются перегруженные операторы, как они ведут себя, как должны соотноситься друг с другом и как всем этим можно управлять. Освоив материалы данной главы, вы будете перегружать (или не перегружать) операторы с той же уверенностью, что и настоящий профессионал. Правило 5. Опасайтесь определяемых пользователем функций преобразования типа Язык С++ позволяет компиляторам осуществлять неявное преобразование типов. Так же как и его предшественник, язык С, он позволяет неявное преобразование из char в int или из short в double. Вот почему можно передать функции, имеющей аргумент типа double, параметр типа short, и несмотря на это вызов функции будет осуществлен корректно. Наиболее страшные по своим последствиям преобразования в С, при которых происходит потеря информации, сохранились и в С++, включая преобразование из int в short и из double в (самый распространенный вариант) char. С такими преобразованиями ничего нельзя поделать, они встроены в язык. Однако при добавлении собственных типов программист может лучше управлять ими, выбирая, включать или нет в программу функции, которые компиляторы потом будут использовать для неявного преобразования типов. Существуют два типа функций, позволяющих компиляторам выполнять такие преобразования: конструкторы с единственным аргументом и операторы неявного преобразования типов. Конструктор с единственным аргументом требует при вызове только один аргумент. Такой конструктор может быть объявлен с одним или несколькими параметрами при условии, что все параметры, начиная со второго, имеют значение по умолчанию. Вот два примера: class Name { Описывает имена объектов. public: Name(conststring&s); Преобразование stringв Name. classRational{ Класс действительныхчисел, public: Rational(intnumerator = 0, Преобразует int denominator = 1) ; int в Rational. Оператор неявного преобразования типа - это просто функция, являющаяся членом класса со странным именем operator, за которым следует спецификация типа. Нельзя задать тип возвращаемого значения, потому что данный тип представляет собой просто имя функции. Например, чтобы обеспечить неявное преобразование объектов типа Rational к типу double (что позволяет вычислять арифметические выражения смешанного типа с объектами типа Rational), можно определить класс Rational следующим образом: classRational { public: operator double()const; Преобразует Rational }; ктипу double. Эта функция будет автоматически вызываться в следующем контексте: Rational г (1,2) ; Значение г равно 1/2 . double d= О . 5 * г; Преобразует г ктипу double, а затем выполняет операцию умножения. Приведенные фрагменты кода, вероятно, показались вам похожими на повторение пройденного. Это хорошо, потому что я хочу объяснить, почему программисты обычно не хотят вводить никакие функции преобразования типа. Главная проблема состоит в том, что появление таких функций приводит к их вызову тогда, когда вы и не ожидаете, и не желаете этого. Результат часто оказывается неверным, а поведение программы не поддается интуитивному анализу, и ее безумно сложно отлаживать. Давайте сначала займемся простейшим слзаем - операторами неявного преобразования типа. Допустим, вы хотите, чтобы класс рациональных чисел, похожий на описанный выше, выводил на печать объекты Rational, как будто это объекты одного из встроенных типов. То есть вы хотите иметь возможность выполнить следующее: Rational г (1,2) ; cout г; Должно выводить 1/2 . Предположим далее, что вы забыли определить operator << для объекта Rational. Вероятно, вы полагаете, что попытка вывести г на печать окончится неудачей, ведь operator << не определен. Вы ошиблись. Ваши компиляторы, обрабатывая вызов функции с именем operator << и аргументом Rational, обнаружат, что такая функция не определена, и попытаются найти подходящую последовательность операторов неявного преобразования типа, которая обеспечит выполнение вызова. Правила подбора подходящей последовательности достаточно сложны, но в этом конкретном случае компиляторы обнаружат, что вызов можно выполнить, если неявно преобразовать г в double, вызвав Rational: :operator double. В результате выполнения программы, приведенной выше, значение г будет напечатано как число с плавающей точкой, а не как действительное число. Это вряд ли повлечет за собой катастрофические последствия, но, тем не менее, хорошо демонстрирует, какие неприятности могут доставить операторы неявного преобразования типа: они могут привести к вызову неверной функции (то есть не той, которую хотел вызвать программист). Решение проблемы состоит в замене операторов на эквивалентные функции, не имеющие аналогов с тем же именем. Например, для приведения объекта Rational к типу double заменим operator double на функцию с именем asDouble: class Rational{ public: doubleasDouble() const; Приведение от типа / / Rational к типу double. Такие функции-члены должны вызываться явно: Rational г(1,2); cout << г; / / Ошибка! Оператор << для / / Rat ionalне определен. cout << г.asDouble(); / / Нормально, г будет напечатано как double . В большинстве случаев неудобство от необходимости вызывать функции преобразования типов явно более чем компенсируется исчезновением нежелательных вызовов функций. Вообще говоря, чем опытнее программисты на языке С++, тем реже они прибегают к операторам преобразования типа. Например, члены комитета, работающие над стандартной библиотекой С++ (см. правило 35),
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |