С++ для начинающих

       

Определенные пользователем преобразования


Мы уже видели, как преобразования типов применяются к операндам встроенных типов: в разделе 4.14 этот вопрос рассматривался на примере операндов встроенных операторов, а в разделе 9.3 – на примере фактических аргументов вызванной функции для приведения их к типам формальных параметров. Рассмотрим с этой точки зрения следующие шесть операций сложения:

char ch; short sh;, int ival;

/* в каждой операции один операнд

 * требует преобразования типа */

ch + ival;          ival + ch;

ch + sh;            ch + ch;

ival + sh;          sh + ival;

Операнды ch и sh расширяются до типа int. При выполнении операции складываются два значения типа int. Расширение типа неявно выполняется компилятором и для пользователя прозрачно.

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

Напомним, что SmallInt позволяет определять объекты, способные хранить значения из того же диапазона, что unsigned char, т.е. от 0 до 255, и перехватывает ошибки выхода за его границы. Во всех остальных отношениях этот класс ведет себя точно так же, как unsigned char.

Чтобы иметь возможность складывать объекты SmallInt с другими объектами того же класса или со значениями встроенных типов, а также вычитать их, реализуем шесть операторных функций:

class SmallInt {

   friend operator+( const SmallInt &, int );

   friend operator-( const SmallInt &, int );

   friend operator-( int, const SmallInt & );

   friend operator+( int, const SmallInt & );

public:

   SmallInt( int ival ) : value( ival ) { }

   operator+( const SmallInt & );

   operator-( const SmallInt & );

   // ...

private:

   int value;

};

Операторы-члены дают возможность складывать и вычитать два объекта SmallInt. Глобальные же операторы-друзья позволяют производить эти операции над объектами данного класса и объектами встроенных арифметических типов. Необходимо только шесть операторов, поскольку любой встроенный арифметический тип может быть приведен к типу int. Например, выражение


SmallInt si( 3 );

si + 3.14159

разрешается в два шага:

1.      Константа 3.14159 типа double преобразуется в целое число 3.

2.      Вызывается operator+(const SmallInt &,int), который возвращает значение 6.

Если мы хотим поддержать битовые и логические операции, а также операции сравнения и составные операторы присваивания, то сколько же необходимо перегрузить операторов? Сразу и не сосчитаешь. Значительно удобнее автоматически преобразовать объект класса SmallInt в объект типа int.

В языке C++ имеется механизм, позволяющий в любом классе задать набор преобразований, применимых к его объектам. Для SmallInt мы определим приведение объекта к типу int. Вот его реализация:

class SmallInt {

public:

   SmallInt( int ival ) : value( ival ) { }

   // конвертер

   // SmallInt ==> int

   operator int() { return value; }

   // перегруженные операторы не нужны

private:

   int value;

};

Оператор int() – это конвертер, реализующий определенное пользователем преобразование,  в данном случае приведение типа класса к заданному типу int. Определение конвертера описывает, что означает преобразование и какие действия компилятор должен выполнить для его применения. Для объекта SmallInt смысл преобразования в int заключается в том, чтобы вернуть число типа int, хранящееся в члене value.

Теперь объект класса SmallInt можно использовать всюду, где допустимо использование int. Если предположить, что перегруженных операторов больше нет и в SmallInt определен конвертер в int, операция сложения

SmallInt si( 3 );

si + 3.14159

разрешается двумя шагами:

1.      Вызывается конвертер класса SmallInt, который возвращает целое число 3.

2.      Целое число 3 расширяется до 3.0 и складывается с константой двойной точности 3.14159, что дает 6.14159.

Такое поведение больше соответствует поведению операндов встроенных типов по сравнению с определенными ранее перегруженными операторами. Когда значение типа int складывается со значением типа double, то выполняется сложение двух чисел типа double (поскольку тип int расширяется до double) и результатом будет число того же типа.



В этой программе иллюстрируется применение класса SmallInt:

#include <iostream>

#include "SmallInt.h"

int main() {

   cout << "Введите SmallInt, пожалуйста: ";

   while ( cin >> si1 ) {

      cout << "Прочитано значение "

           << si1 << "\nОно ";

      // SmallInt::operator int() вызывается дважды

      cout << ( ( si1 > 127 )

              ? "больше, чем "

              : ( ( si1 < 127 )

                ? "меньше, чем "

                : "равно ") ) << "127\n";

      cout << "\Введите SmallInt, пожалуйста \

              (ctrl-d для выхода): ";

   }

   cout <<"До встречи\n";

}

Откомпилированная программа выдает следующие результаты:

Введите SmallInt, пожалуйста: 127

Прочитано значение 127

Оно равно 127

Введите SmallInt, пожалуйста (ctrl-d для выхода): 126

Оно меньше, чем 127

Введите SmallInt, пожалуйста (ctrl-d для выхода): 128

Оно больше, чем 127

Введите SmallInt, пожалуйста (ctrl-d для выхода): 256

*** Ошибка диапазона SmallInt: 256 ***

В реализацию класса SmallInt добавили поддержку новой функциональности:

#include <iostream>

class SmallInt {

   friend istream&

      operator>>( istream &is, SmallInt &s );

   friend ostream&

      operator<<( ostream &is, const SmallInt &s )

      { return os << s.value; }

public:

   SmallInt( int i=0 ) : value( rangeCheck( i ) ){}

   int operator=( int i )

      { return( value = rangeCheck( i ) ); }

   operator int() { return value; }

private:

   int rangeCheck( int );

   int value;

};

Ниже приведены определения функций-членов, находящиеся вне тела класса:

istream& operator>>( istream &is, SmallInt &si ) {

   int ix;

   is >> ix;

   si = ix;       // SmallInt::operator=(int)

   return is;

}

int SmallInt::rangeCheck( int i )

{

/* если установлен хотя бы один бит, кроме первых восьми,

 * то значение слишком велико; сообщить и сразу выйти */

   if ( i & ~0377 ) {

      cerr << "\n*** Ошибка диапазона SmallInt: "

           << i << " ***" << endl;

      exit( -1 );

   }

   return i;

}


Содержание раздела