|
Программирование >> Инициализация объектов класса, структура
ошибка: отсутствует конструктор класса AAndQuery AAndQuery proust( new NameQuery( marcel ), фактический объект: new NameQuery( proust )); то компилятор выдаст сообщение об ошибке: в классе AndQuery нет конструктора, готового принять два аргумента. Мы предположили, что AndQuery и OrQuery наследуют конструктор BinaryQuery точно так же, как они наследуют функции-члены lop() и rop() . Однако производный класс не наследует конструкторов базового. (Это могло бы привести к ошибкам, связанным с неинициализированными членами производного класса. Представьте, что будет, если в AndQuery добавить пару членов, не являющихся объектами классов: унаследованный конструктор базового класса для инициализации объекта производного AndQuery применять уже нельзя. Однако программист может этого не осознавать. Ошибка проявится не при конструировании объекта AndQuery, а позже, при его использовании. Кстати говоря, перегруженные операторы new и delete наследуются, что иногда приводит к аналогичным проблемам.) Каждый производный класс должен предоставлять собственный набор конструкторов. В случае классов AndQuery и OrQuery единственная цель конструкторов - обеспечить интерфейс для передачи двух своих операндов конструктору BinaryQuery. Так выглядит исправленная реализация: Складывается впечатление, что теперь оба производных класса должны предоставить увы! эти определения классов некорректно! class OrQuery : public BinaryQuery { public: virtual void eval(); class AndQuery : public BinaryQuery { public: virtual void eval(); лишь подходящие реализации eval (): Однако в том виде, в котором мы их определили, эти классы неполны. При компиляции самих определений ошибок не возникает, но если мы попытаемся определить правильно: эти определения классов корректно! class OrQuery : public BinaryQuery { public: OrQuery( Query *lop, Query *rop ) : BinaryQuery( lop, rop ) rop ) {} virtual void eval(); class AndQuery : public BinaryQuery { public: AndQuery( Query *lop, Query *rop ) : BinaryQuery( lop, rop ) {} virtual void eval(); Если мы еще раз взглянем на рис. 17.2, то увидим, что BinaryQuery - непосредственный базовый класс для AndQuery и OrQuery, а Query -для BinaryQuery. Таким образом, Query не является непосредственным базовым классом для AndQuery и OrQuery. Конструктору производного класса разрешается напрямую вызывать только конструктор своего непосредственного предшественника в иерархии (виртуальное наследование является исключением из этого правила, да и из многих других тоже: см. раздел 18.5). Например, попытка включить конструктор Query в список инициализации членов объекта AndQuery приведет к ошибке. При определении объектов классов AndQuery и OrQuery теперь вызываются три конструктора: для базового Query, для непосредственного базового класса BinaryQuery и для производного AndQuery или OrQuery. (Порядок вызова конструкторов базовых классов отражает обход дерева иерархии наследования в глубину.) Дополнительный уровень иерархии, связанный с BinaryQuery, практически не влияет на производительность, поскольку мы определили его конструкторы как встроенные. Так как модифицированная иерархия сохраняет открытый интерфейс исходного проекта, то все эти изменения не сказываются на коде, который был написан в расчете на старую иерархию. Хотя модифицировать пользовательский код не нужно, перекомпилировать его все же придется, что может отвратить некоторых пользователей от перехода на новую версию. 17.4.4. Отложенное обнаружение ошибок Начинающие программисты часто удивляются, почему некорректные определения классов AndQuery и OrQuery (в которых отсутствуют необходимые объявления конструкторов) компилируются без ошибок. Если бы мы не попытались определить фактический объект класса AndQuery, в этой модифицированной иерархии так и осталась бы ненайденная ошибка. Дело в том, что: если ошибка обнаруживается в точке объявления, то mi не можем продолжать компиляцию приложения, пока не исправим ее. Если же конфликтующее объявление - это часть библиотеки, для которой у нас нет исходного текста, то разрешение конфликта может оказаться нетривиальной задачей. Более того, возможно, в нашем коде никогда и не возникнет ситуации, когда эта ошибка проявляется, так что для нас она останется лишь потенциальной угрозой; inline Query:: ~Query(){ delete solution; } inline NotQuery:: ~NotQuery(){ delete op; } inline OrQuery:: ~OrQuery(){ delete lop; delete rop; } inline AAndQuery:: открытыми членами соответствующих классов): ~A\ndQuery(){ delete lop; delete rop; } Отметим два аспекта: с другой стороны, если ошибка не найдена вплоть до момента использования, то код может оказаться замусоренным ошибками, проявляющимися в самый неподходящий момент к удивлению программиста. При такой стратегии успешная компиляция говорит не об отсутствии семантических ошибок, а лишь о том, что программа не исполняет код, нарушающий семантические правила языка. Выдача сообщения об ошибке в точке использования - это одна из форм отложенного вычисления, распространенного метода повышения производительности программ. Он часто применяется для того, чтобы отложить потенциально дорогую операцию выделения или инициализации ресурса до момента, когда в нем возникнет реальная необходимость. Если ресурс так и не понадобится, мы сэкономим на ненужных подготовительных операциях. Если же он потребуется, но не сразу, м1 растянем инициализацию программа: на более длительный период. В C++ потенциальные ошибки комбинирования , связанные с перегруженными функциями, шаблонами и наследованием классов, обнаруживаются в точке использования, а не в точке объявления. (Мы полагаем, что это правильно, поскольку необходимость выявлять все возможные ошибки, которые можно допустить в результате комбинирования многочисленных компонентов, - пустая трата времени). Следовательно, для обнаружения и устранения латентных ошибок необходимо тщательно тестировать код. Подобные ошибки, возникающие при комбинировании двух или более больших компонентов, допустимы; однако в пределах одного компонента, такого, как иерархия классов Query, их быть не должно. 17.4.5. Деструкторы Когда заканчивается время жизни объекта производного класса, автоматически вызываются деструкторы производного и базового классов (если они определены), а также деструкторы всех объектов-членов. Например, если имеется объект класса NameQuery: NameQuery nq( hyperion ); то порядок вызова деструкторов следующий: сначала деструктор NameQuery, затем деструктор string для члена name и наконец деструктор базового класса. В общем случае эта последовательность противоположна порядку вызова конструкторов. Вот деструкторы нашего базового Query и производных от него (все они объявлены
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |