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

1 ... 89 90 91 [ 92 ] 93 94 95 ... 144


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. Я думал, что



1 ... 89 90 91 [ 92 ] 93 94 95 ... 144

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