Программирование >>  Полиморфизм без виртуальных функций в с++ 

1 ... 23 24 25 [ 26 ] 27 28 29 ... 144


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

Естественно, не все конструкторы определяют разумное и предсказуемое преобразование. Например, у типа vector обычно есть конструктор, которому передается целый аргумент, обозначающий число элементов. Если бы предложение 4=1 конструировало вектор из семи элементов и присваивало его v, это следоваао бы счесть нежелательным побочным эффектом. Я не считал проблему первостепенной. Однако несколько членов ко.митета по стандартизации С++, в первую очередь Натан Майерс (Nathan Myers), настаивали на необходимости срочного решения. Оно было найдено уже в 1995 г.: в объявление конструктора разреп]или включать префикс explicit. Конструктор, объявленный как explicit, используется только для явного конструирования, но не для неявных преобразований. Например, если в объявлении класса vector и.меется конструктор explicit vector (int) ;, то предложение v=7 вызовет ошибку компиляции, тогда как более явное v=vector(7) приведет к нормальному конструированию вектора и присваиванию его v.

3.6.2. Функции-члены и дружественные функции

Обратите внимание на то, что в.место функции-члена используется глобальная дружественная (friend) функция operator+. Это обеспечивает симметричность операндов для операции +. При использовании функций-членов было бы необходимо разрешать имена:

void f(complex zl, complex z2, double d) {

complex z3 = zl+z2; zl.operator+(z2) complex z4 = zl+d; zl.operator+(complex(d)) complex z5 = d+z2; d.operator+(z2)

Тогда потребовалось бы определить, как соединяются объекты типа complex с объектами встроенного типа double. Для этого понадобилось бы увеличить число функций и модифицировать код в разных .местах (в определении класса complex и встроенного типа double). Вариант был сочтен нежелательны.м. Я думал о том, чтобы разрешить определение дополнительных операций над встроенными типами, но отверг эту идею, поскольку не хотел нарушать правило, гласящее, что после определения любого типа - встроенного или определенного пользователем - добавление операций с ним уже невозможно. Были и другие причины:

□ определение преобразований между встроенными типами и так достаточно запутано, чтобы добавлять еще что-либо;

□ обеспечение смешанной арифметики с помощью функций-членов менее прозрачно, чем решение с помощью сочетания глобальной функции и конвертора.



Использование глобальной функции позволяет определить операторы таким образом, что их аргументы логически эквивалентны. Наоборот, определение оператора в виде функции-члена гарантирует, что для первого (левого) операнда никакие преобразования вызываться не будут. Таким образом, правила имеют зеркальное отражение для операторов, где левый операнд - lvalue:

class String {

... public:

Stringlconst char*);

Strings operator=(const Strings);

Strings operator+=(const String&); добавить в конец

. . .

void f(Strings si. Strings s2) {

si = S2;

sl = asdf ; правильно: si.operator=(String( asdf )); asdf = s2; ошибка: String присваивается char*

Позже Эндрю Кениг заметил, что операторы присваивания, такие как +=, более фундаментальны и эффективны, чем арифметические операторы вроде +. Часто лучше определить только функции типа + = и * = в виде членов, а обычные операторы типа + и * добавить позже как глобальные функции:

strings String::operator+=(const Strings s) {

добавить s в конец *this return *this;

String operator+(const Strings sl, const Strings s2) (

String sum = sl; sum+=s2; return sum;

Заметим, что модификатор friend здесь не нужен и что определение бинарного оператора тривиально. Для реализации вызова += вообше не требуются временные переменные, а локальная переменная sum в реализации + - это единственный временный объект, с которым сталкивается пользователь. Об остальном позаботится компилятор (см. раздел 3.6.4).

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



к операторам вроде + и - рассуждения были верны, но в отношении оператора = мы столкнулись с некоторыми проблемами. Поэтому в версии 2.0 требовалось, чтобы оператор = был членом. Такое изменение было несовместимо с предыдущи.ми версиями, и несколько программ перестало работать. Одним словом, решение далось нам нелегко. Ведь если оператор = не был членом, то в программе он мог по-разному интерпретироваться в зависимости от места в исходном коде. Вот пример:

class X {

нет оператора =

void f(X а, X b) {

а = b; предопределенная семантика =

void operator=(X&,X); запрещено в 2.0

void g(X а, X b) {

а = b; определенная пользователем семантика =

Это могло послужить причиной серьезных недоразумений, особенно если оба присваивания находились в разных исходных файлах. Поскольку для оператора += предопределенной семантики класса не существует, то проблем здесь и не возникает.

Однако даже при первоначальном проектировании С-и- я предусмотрел следующее ограничение: операторы [], {) и-> должны быть членами. Мне казалось, что оно безобидно и в то же время исключает возможность некоторых ошибок, связанных с тем, что указанные операторы всегда зависят от состояния левого операнда и, как правило, модифицируют его. Впрочем, тут я проявил чрезмерную заботу о пользователях.

3.6.3. Операторные функции

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

class complex { ...

complex(double); преобразует double в complex ...

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



1 ... 23 24 25 [ 26 ] 27 28 29 ... 144

© 2006 - 2025 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика