|
Программирование >> Оптимизация возвращаемого значения
Фактически, если задана последняя функция operator=, на ее основе чрезвычайно просто реализовать предыдущую функцию: LizardSc Lizard::operator={const Animals rhs) { return operator={dYnamic cast<const Lizard&>{rhs)); Эта функция пытается привести значение rhs к типу Lizard. Если такое приведение успешно, вызывается обычный оператор присваивания класса. В противном случае генерируется исключение bad cast. Честно говоря, вся эта деятельность по проверке типов во время выполнения программы и использование операторов dynamic cast меня беспокоит. Во-первых, некоторые компиляторы все еще не поддерживают dynamic cast, поэтому теоретически переносимый код, использующий их, на практике не обязательно будет таковым. Что более важно, клиенты классов Lizard и Chicken должны быть готовы перехватывать и обрабатывать при выполнении присваивания исключения bad cast. Судя по моему опыту, немного найдется программистов, которые любят писать программы подобным образом. Если же не сделать этого, то неочевидно, выиграем ли мы что-либо по сравнению с исходной ситуацией, когда пытались избежать частичного присваивания. При таком неудовлетворительном положении дел с виртуальными операторами присваивания имеет смысл перенаправить усилия и попытаться, прежде всего, найти способ предотвратить выполнение пользователями рискованного присваивания. Если такое присваивание будет отвергаться во время компиляции, вам не придется беспокоиться о том, что оно сделает что-то не так. Простейший способ не допустить такого присваивания - сделать функцию operator= в классе Animal закрытой. Тогда значение ящерицы можно будет присваивать ящерицам, а куры - курам, но частичные и смешанные присваивания будут запрещены: class Animal { private: Animals operator=(const Animals rhs); Теперь эта функция ... является закрытой. class Lizard: public Animal { public: Lizards operator=(const Lizards rhs) ; class Chicken: public Animal { public: Chickens operator=(const Chickens rhs) ; Lizard lizl, liz2 ; Нормально. Также нормально. lizl = liz2; Chicken chickl, chick2; chickl = chick2; Animal *pAnimall = &lizl; Animal *pAnimal2 = &chickl; *pAnimall = *pAnimal2; Ошибка! Попытка вызова закрытой функции Animal::operator=. К сожалению, класс Animal является реальным, и такой подход запрещает присваивание между объектами Animal: Animal animall, animal2; animall = animal2; Ошибка! Попытка вызова закрытой функции Animal::operator=. Более того, это делает невозможной корректную реализацию операторов присваивания классов Lizard и Chicken, поскольку операторы присваивания в производных классах отвечают за вызов операторов присваивания в своих базовых классах: LizardSc Lizard: :operator= (const LizardSc rhs) if (this == Scrhs) return *this; Animal::operator=(rhs); Ошибка! Попытка вызова закрытой функции. Но Lizard::operator= должен вызывать эту функцию для присваивания частей Animal в объекте *this! Нетрудно решить последнюю проблему, объявив Animal: :operator= как protected, но головоломка - как сделать так, чтобы можно было присваивать объекты Animal, запретив частичное присваивание объектов Lizard и Chicken при помощи указателей Animal, - остается нерешенной. Что делать бедному программисту? Проще всего устранить необходимость разрешать присваивание между объектами Animal, например, сделав класс Animal абстрактным. Тогда нельзя будет создавать экземпляры класса Апл mal, поэтому не нужно будет разрешать присваивание между объектами Animal. Конечно же, это приводит к новой проблеме, так как первоначальная схема предполагала необходимость создания объектов Animal. Существует простой способ обойти данное затруднение. Вместо того чтобы делать абстрактным сам класс Animal, можно создать новый класс, скажем, Abstract-Рис. 6.2 Animal, состоящий из общих свойств объектов W.:;lr;.c;tAriim:il ) Animal, Lizard и Chicken, и объявить абстрактным этот гсласс. Тогда каждый из реальных классов будет наследовать от класса AbstractAnimal. Исправленная иерархия показана на рис. 6.2. А определения классов выглядят следующим образом: class AbstractAnimal { protected: AbstractAnimal& operator= (const AbstractAnimal& rhs) ; public: virtual -AbstractAnimal () =0; См. ниже. class Animal: public AbstractAnimal { public: Animal& operator(const Animals rhs); class Lizard: public AbstractAnimal { public: Lizards operator=(const Lizards rhs); class Chiclten: public AbstractAnimal { public: ChicltenS operator= (const ChicltenS rhs) ; Эта схема дает нам все, что нужно. Для классов Lizard, Chicken, Animal разрешены однотипные присваивания; частичные и разнотипные присваивания запрещены; и операторы присваивания в производных классах могут вызывать операторы присваивания в базовом классе. Более того, код, написанный на основе классов Animal, Lizard или Chicken не требует изменений, поскольку эти классы продолжают существовать и вести себя так, как они вели себя до введения класса AbstractAnimal. Код, конечно, придется перекомпилировать, но это не слишком большая цена за уверенность в том, что перекомпилированные присваивания будут вести себя интуитивно понятно, а те, которые ведут себя неправильно, не станут компилироваться. Чтобы все это работало, класс AbstractAnimal должен быть абстрактным -он должен содержать хотя бы одну абстрактную функцию. В большинстве случаев создание подходящей функции не является проблемой, но иногда возникает необходимость в классе типа AbstractAnimal, где, естественно, ни одна функция не может быть объявлена абстрактной. В этом случае обычно объявляется абстрактным деструктор, как и показано выше. Чтобы корректно поддержать полиморфизм при помощи указателей, базовые классы все равно должны иметь виртуальные деструкторы, поэтому единственные затраты на абстрагирование деструкторов состоят в неудобстве их реализации вне определений классов. (См., например, стр. 201.)
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |