Основы программирования

Новые типы С++ - Конструкторы
Индекс материала
Новые типы С++
Классы и Члены
Классы
Инициализация
Очистка
Интерфейсы и Реализации
Законченный Класс
Друзья и Объединения
Вложенные Классы
Структуры и Объединения
Конструкторы и Деструкторы
Предостережение
Свободная Память
Вектора Объектов Класса
Небольшие Объекты
Предостережение
Упражнения
Перегрузка Операций
Функции Операции
Предопределенный Смысл Операций
Конструкторы
Константы
Присваивание и Инициализация
Индексирование
Вызов Функции
Класс String
Синтаксис вызова функции
Предостережение
Упражнения
Производные Классы
Производные Классы
Функции
Видимость
Указатели
Иерархия Типов
Конструкторы и Деструкторы
Поля Типа
Виртуальные Функции
Альтернативные Интерфейсы
Реализация
Как Этим Пользоваться
Обработка Ошибок
Обобщенные Классы
Ограниченные Интерфейсы
Добавление к Классу
Неоднородные Списки
Администратор Экрана
Библиотека Фигур
Прикладная Программа
Свободная Память
Упражнения
Все страницы


6.3.1 Конструкторы

Альтернативу использованию нескольких функций (перегрженных) составляет описание конструктора, который по заданнму double создает complex. Например:

class complex (* // ... complex(double r) (* re=r; im=0; *) *);

Конструктор, требующий только один параметр, необязтельно вызывать явно:

complex z1 = complex(23); complex z2 = 23;

И z1, и z2 будут инициализированы вызовом complex(23).

Конструктор – это предписание, как создавать значение данного типа. Когда требуется значение типа, и когда такое значение может быть создано конструктором, тогда, если такое значение дается для присваивания, вызывается конструктор. Например, класс complex можно было бы описать так:

class complex (* double re, im; public: complex(double r, double i = 0) (* re=r; im=i; *)

friend complex operator+(complex, complex); friend complex operator*(complex, complex); *);

и действия, в которые будут входить переменные complex и целые константы, стали бы допустимы. Целая константа будет интерпретироваться как complex с нулевой мнимой частью. Наример, a=b*2 означает:

a=operator*( b, complex( double(2), double(0) ) )

Определенное пользователем преобразование типа применется неявно только тогда, когда оно является единственным.

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



6.3.2 Операции Преобразования

Использование конструктора для задания преобразования типа является удобным, но имеет следствия, которые могут окзаться нежелательными:

1. Не может быть неявного преобразования из определеного пользователем типа в основной тип (поскольку осноные типы не являются классами)

2. Невозможно задать преобразование из нового типа в старый, не изменяя описание старого

3. Невозможно иметь конструктор с одним параметром, не имея при этом преобразования.

Последнее не является серьезной проблемой, а с первыми двумя можно справиться, определив для исходного типа операцию преобразования. Функция член X::operator T(), где T – имя тпа, определяет преобразование из X в T. Например, можно опрделить тип tiny (крошечный), который может иметь значение только в диапазоне 0...63, но все равно может свободно сочтаться в целыми в арифметических операциях:


class tiny (* char v; int assign(int i) (*return v=(i amp;~63) ? (error(«ошибка диапазона»),0):i;*) public: tiny(int i) (* assign(i); *) tiny(tiny amp; i) (* v = t.v; *) int operator=(tiny amp; i) (* return v = t.v; *) int operator=(int i) (* return assign(i); *) operator int() (* return v; *) *)

Диапазон значения проверяется всегда, когда tiny иницилизируется int, и всегда, когда ему присваивается int. Одно tiny может присваиваться другому без проверки диапазона. Чтбы разрешить выполнять над переменными tiny обычные целые операции, определяется tiny::operator int(), неявное преобрзование из tiny в int. Всегда, когда в том месте, где требется int, появляется tiny, используется соответствующее ему int. Например:

void main() (* tiny c1 = 2; tiny c2 = 62; tiny c3 = c2 – c1; // c3 = 60 tiny c4 = c3; // нет проверки диапазона (необязательна) int i = c1 + c2; // i = 64 c1 = c2 + 2 * c1; // ошибка диапазона: c1 = 0 (а не 66) c2 = c1 -i; // ошибка диапазона: c2 = 0 c3 = c2; // нет проверки диапазона (необязательна) *)

Тип вектор из tiny может оказаться более полезным, покольку он экономит пространство. Чтобы сделать этот тип более удобным в обращении, можно использовать операцию индексировния.

Другое применение определяемых операций преобразования – это типы, которые предоставляют нестандартные представления чисел (арифметика по основанию 100, арифметика, арифметика с фиксированной точкой, двоично-десятичное представление и т.п.). При этом обычно переопределяются такие операции, как + и *.

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

Типы istream и ostream опираются на функцию преобразовния, чтобы сделать возможными такие операторы, как

while (cin»»x) cout««x;

Действие ввода cin»»x выше возвращает istream amp;. Это знчение неявно преобразуется к значению, которое указывает сотояние cin, а уже это значение может проверяться оператором while (см. #8.4.2). Однако определять преобразование из оного типа в другой так, что при этом теряется информация, обычно не стоит.



6.3.3 Неоднозначности

Присваивание объекту (или инициализация объекта) класса X является допустимым, если или присваиваемое значение является X, или существует единственное преобразование присваивемого значения в тип X. В некоторых случаях значение нужного типа может быть построено с помощью нескольких применений конструкторов или операций преобразования. Это должно делаться явно; допустим только один уровень неявных преобразований, определенных пользователем. Иногда значение нужного типа может быть посроено более чем одним способом. Такие случаи являются недпустимыми. Например:

class x (* /* ... */ x(int); x(char*); *); class y (* /* ... */ y(int); *); class z (* /* ... */ z(x); *);

overload f; x f(x); y f(y);

z g(z);

f(1); // недопустимо: неоднозначность f(x(1)) или f(y(1)) f(x(1)); f(y(1)); g(«asdf»); // недопустимо: g(z(x(«asdf»))) не пробуется g(z(«asdf»));

Определяемые пользователем преобразования рассматриваюся только в том случае, если без них вызов разрешить нельзя. Например:

class x (* /* ... */ x(int); *) overload h(double), h(x); h(1);

Вызов мог бы быть проинтерпретирован или как h(double(1)), или как h(x(1)), и был бы недопустим по правилу единственности. Но первая интерпретация использует только стандартное преобразование и она будет выбрана по правилам, приведенным в #4.6.7.

Правила преобразования не являются ни самыми простыми для реализации и документации, ни наиболее общими из тех, кторые можно было бы разработать. Возьмем требование единтвенности преобразования. Более общий подход разрешил бы копилятору применять любое преобразование, которое он сможет найти; таким образом, не нужно было бы рассматривать все воможные преобразования перед тем, как объявить выражение дпустимым. К сожалению, это означало бы, что смысл программы зависит от того, какое преобразование было найдено. В резултате смысл программы неким образом зависел бы от порядка опсания преобразований. Поскольку они часто находятся в разных исходных файлах (написанных разными людьми), смысл программы будет зависеть от порядка компоновки этих частей вместе. Есть другой вариант – запретить все неявные преобразования. Нет ничего проще, но такое правило приведет либо к неэлегантным пользовательским интерфейсам, либо к бурному росту перегрженных функций, как это было в предыдущем разделе с complex.

Самый общий подход учитывал бы всю имеющуюся информацию о типах и рассматривал бы все возможные преобразования. Напрмер, если использовать предыдущее описание, то можно было бы обработать aa=f(1), так как тип aa определяет единственность толкования. Если aa является x, то единственное, дающее в рзультате x, который требуется присваиванием, – это f(x(1)), а если aa – это y, то вместоэтого будет использоваться f(y(1)). Самый общий подход справился бы и с g(«asdf»), поскольку единственной интерпретацией этого может быть g(z(x(«asdf»))). Сложность этого подхода в том, что он требует расширенного нализа всего выражения для того, чтобы определить интерпретцию каждой операции и вызова функции. Это приведет к замедлнию компиляции, а также к вызывающим удивление интерпретацим и сообщениям об ошибках, если компилятор рассмотрит преоразования, определенные в библиотеках и т.п. При таком подхде компилятор будет принимать во внимание больше, чем, как можно ожидать, знает пишущий программу программист!