|
Программирование >> Полиморфизм без виртуальных функций в с++
void g(); АО ; А: :А0 fО; ошибка: вызвана исключительно виртуальная функция gО; выглядит вполне безобидно Незаконный вызов А: : f (} легко обнаруживается компилятором. Однако А: :д () может быть объявлена, скажем, как void А::g() { f(); } в какой-то другой единице трансляции. Такую ошибку сможет обнаружить лишь компилятор, выполняющий перекрестны!! анализ нескольких единиц трансляции. Альтернативой же является ошибка во время выполнения. 13.2.4.2. Конструирование в порядке сначала базовый Я всегда предпочитаю проект, не оставляюпщй места для ошибок во время выполнения. Но пет способа сделать програ.ммирование абсолютно безопасным. В частности, конструкторы создают среду, в которой работают функции-члены (см. раздел 2.11.1). Однако пока создание такой среды не завершено, програ.ммис-ту даются частичные гарантии. Рассмотрим пример: class 3 { public: int b; virtual void f(); void gO ; . . . BO ; class D : public В { public: X x; void f() ; . . . DO ; B: :B0 b++; неопределенное поведение: B::b еще не инициализирована f(); вызывается: B::f(), анеО::£0 Компилятор может выдать предупреждения об обеих потенциальных проблемах. Если вы действительно собирались вызвать функцию f () из самого класса в, напишите это явно - в: : f (}. Поведение данного конструктора контрастирует с тем, как пишется обыкновенная функция (зависящая от правильной работы конструктора): void В::g() { b++; правильно, так как В::Ь - это член В::В должен инициализировать его f (); вызывается: D::f(), если для D вызывается В::д Разница между тем, какую функцию вызывает f () при обращении из В: : В () и из В: : g () в случае, когда В есть часть D, может удивить неискушенного пользователя. 13.2.4.3. Что, если?.. Рассмотрим, что произошло бы при выборе альтернативного решения, когда каждый вызов виртуальной функции f () приводит к вызову замещающей: void D::fО { операция, написанная из расчета на то, что D::X правильно инициализирован в D::D Если бы замещающую функцию допускалось вызывать в процессе конструирования, ни в одной виртуальной функции не было бы гарантии, что объект будет правильно инициализирован конструкторами. Поэтому в любой замещающей функции пришлось бы так же аккуратно контролировать все ошибки, как это обычно делают в конструкторах. На самом деле написание замещающей функции оказалось бы сложнее, чем написание конструкторов, так как в последнем сравнительно просто определить, что было проинициализировано, а что нет. Автор замещающей функции, не имеющий гарантий того, что конструктор уже отработал, мог бы: □ продолжать неоправданно надеяться на завершение инициализации; □ пытаться защититься от неинициализированных базовых классов и членов. Первый вариант лишает конструкторы привлекательности. Второй - практически нереализуем, поскольку у производного класса может быть много непосредственных и косвенных базовых классов, а во вре.мя выполнения нет возможности проверить, была ли инициализирована произвольная переменная. void D::f() нехарактерно для С++ { if (base initialized) { действие, опирающееся на то, что D::Х инициализирована D:D else { сделать все возможное, не полагаясь на то, что D::X инициализирована Следовательно, если бы конструкторы вызывали замещающие функции, то для разумного написания последних пришлось бы значительно ограничить использование конструкторов. При проектировании языка я принял такую точку зрения: пока конструктор объекта полностью не отработал, объект надо рассматривать, как недостроенное здание. В обмен на это разрешается считать, что после завершения конструирования объект пригоден к использованию. 13.3. Константные функции-члены в Cfront 1.0 константность поддерживалась не вполне последовательно. Когда реализация стала более строгой, в определении языка обнаружилось несколько прорех. Программисту нужно было предоставить воз.можность определять, какие функции-члены могут изменять состояние объекта, а какие - нет: class X { int аа; public: void update{) { aa++; } int valueO const { return aa; } void cheat 0 const { aa++; } ошибка: *this константный Функция-член, объявленная с модификатором const, например X: rvalue {}, называется константной. Гарантируется, что такая функция не из.меняет значения объекта. Константную функцию-член можно применять как к константным, так и к неконстантным объектам. Но неконстантная функция-член, такая как X: : update {), может применяться только к таким же объектам: int g{X ol, const X& o2) { ol.update 0; правильно o2.updateO; ошибка: o2 - константный объект return ol.valueO + o2.valueO; правильно Технически такая модель обеспечивается за счет того, что указатель this для неконстантной функции-члена класса X указывает на X, а для константной - на const X. Различие между константными и неконстантными функциями позволяет выразить в С-ы- разделение между теми функциями, которые модифицируют состояние объекта, и теми, которые этого не делают. Константные функции-члены оказались среди тех свойств языка, которые получили большую поддержку во время дискуссий на семинаре разработчиков компиляторов в Эстес Парк (см. раздел 7.1.2). 13.3.1. Игнорирование const при приведении типов С++ спроектирован для того, чтобы обнаруживать случайные ошибки, а не предотвращать намеренное мошенничество . Для меня это означало, что должна существовать возможность принудительного игнорирования const. Я думал, что
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |