|
Программирование >> Полиморфизм без виртуальных функций в с++
преобразований и квадратичным, если обходиться без них, становится весьма ощутимой. Я знаю примеры, где приходилось писать полный набор операторов, поскольку не удавалось безопасно определить конверторы. В результате получалось более 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. Однако при этом проектировщик класса определит преобразование только к этому классу. Довольно часто возникает необходи.мость создать новый класс, который вписывается
|
© 2006 - 2025 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |