|
Программирование >> Обобщенные обратные вызовы
написали пустой деструктор самостоятельно, причем такой деструктор может генерировать все исключения, которые могут генерировать деструкторы базовых классов и членов. Неявный деструктор недействителен, если хотя бы один из базовых классов или членов имеет недоступный деструктор, или один из деструкторов базовых классов виртуален и не все деструкторы базовых классов или членов имеют идентичные спецификации исключений (см. раздел Спецификации исключений неявно определенных функций , стр. 127). Член auto ptr 2. Какие функции неявно объявлены и созданы компилятором в следующем классе х? С какими сигнатурами? пример 19-2 class X { auto ptr<int> i ; Небольшое отступление. Этот пример просто иллюстрирует правила, которым подчиняется неявное объявление и определение функций, так что не стоит считать его образцом хорошего стиля. Вообще говоря, применения auto ptr в качестве членов класса (да и в других ситуациях) следует избегать; предпочтительно использовать класс shared ptr, которого еще нет в стандартной библиотеке, но он уже включен в п р е двар ител ьн у ю редакцию следующей версии стандарта С+ + . В классе х неявно объявлены в качестве открытых членов следующие функции (каждая из которых становится неявно определенной, когда написанный вами код пытается ее использовать). inline х::х() throwO : i 0 { } inline х::х( х& other ) throwO : i ( other.i ) { } inline X& X::operator=( X& other ) throwO {i =other.i ; return*thi s;} inline x: :~xO throwQ { } Копирующий конструктор и оператор копирующего присваивания получают в качестве параметров ссылки на неконстантные объекты. Это обусловлено тем, что так же поступают копирующий конструктор и оператор копирующего присваивания auto ptr. Аналогично, все эти функции имеют пустые спецификации исключений, так как в состоянии их обеспечить - ни одна аналогичная операция auto ptr не генерирует исключений (как, впрочем, вообще все операции auto ptr). Заметим, что копирующий конструктор и оператор копирующего присваивания класса auto ptr передают владение. Это может оказаться не тем действием, на которое рассчитывает автор х, так что класс х, скорее всего, должен обеспечивать собственные версии копирующего конструктора и оператора копирующего присваивания (Доп ол и ител ь н у ю информацию по этому вопросу можно найти в [Sutter02].) Семейные проблемы 3. Пусть у вас есть базовый класс, который требует, чтобы все производные классы не ис-нользовали ни одной неявно объя1Вленной и созданной компилятором функции. Например: пример 19-3 class Count { public: этим комментарием автор класса Count документирует, что производные классы должны наследоваться виртуально, и что их конструкторы должны вызывать конструктор класса Count специального назначения. count( /* Специальные параметры */ ); Counts operator=C const Count& ); Обычный virtual ~Count(); Обычный Итак, мы имеем класс, производные классы которого должны вызывать специальный конструктор Count, например, чтобы отслеживать количество объектов производных классов в системе. Это серьезная причина для использования виртуального наследования, которое позволит избежать двойного учета при .множественном наследовании, когда может случиться так, что у некоего производного класса окажется больше одного базового класса Count&. Интересно, заметили ли вы, что в классе count есть ошибка проектирования? У него есть неявно генерируемый копирующий конструктор, что, вероятно, является нежелательным для корректной работы счетчика. Для того чтобы исправить ситуацию, надо просто объявить закрытый копирующий конструктор без его определения: private: не определен; копирующего конструктора нет CountC const Count& ); Итак, мы хотим, чтобы класс count определял поведение дочерних производных классов. Но дети не всегда слушаются родителей, правда? :-) К сожалению, программистам, как и всем остальны.м людям, свойственно ошибаться, так что иногда они будут забывать, что они обязаны явно написать две функции. class BadDerived : private virtual Count { int i.. ; конструктор no умолчанию: должен вызывать специальный конструктор, но делает ли он это? Вкратце - нет, конструктор по умолчанию не вызывает специализированный конструктор. Более того, существует ли вообще конструктор по умолчанию BadDerived? Ответ, который вряд ли покажется вам обнадеживающим, - отчасти. Имеется неявно объявленный конструктор по умолчанию (хорошо), но если вы попытаетесь его вы звать, у вас ничего не получится (плохо). Рассмотрим, почему так получается. Начнем с того, что BadDerived не определяет ни одного собственного конструктора, так что конструктор по умолчаниго будет объявлен неявно. Но в тот момент, когда вы попытаетесь его вызвать (напри.мер, при со,здании объекта BadDerived), этот конструктор по умолчанию становится неявно определенным или, как минимум, должен стать таковым. Однако поскольку неявно определенный конструктор, как предполагается, вызывает конструктор по умолчанию базового класса, который не существует, мы получаем неработоспособную программу. Отсюда можно заключить, что любая программа, которая попытается создать объект BadDerived, не соответствует правилам языка, и класс BadDerived совершенно справедливо назван некорректным. Так есть ли у данного класса конструктор по умолчанию? Отчасти. Он объявлен, по при попытке вызвать его выясняется, что он ни на что не годен. Если дети так себя ведут, такую семью трудно назвать счастливой. Копирующий конструктор: должен вызывать специальный конструктор, но делает ли он это? По тем же причинам неявно сгенерированный копирующий конструктор будет объявлен, но при определении не будет вызывать специальный конструктор count. Как видно из исходного определения класса count, этот копирующий кон- Этот пример адаптирован из кода, приведенного в неопубликованной статье Марко Далла Гасперина (Marco Dalla Gasperina) Подсчет объектов и виртуатьное наследование . Его код не имеет ошибок проектирования, о которых пойдет речь дальше. Тема этой статьи несколько отличается от рассматриваемой в данной задаче, но этот пример вполне применим для нее. структор будет просто вызывать неявно сгенерированный копирующий конструктор класса count. Если нам надо подавить неявно генерируемый копирующий конструктор count, как показано ранее, то класс BadDerived будет иметь неявно объявленный копирующий конструктор, но поскольку он не может быть неявно определен (т.к. копирующий конструктор Count оказывается недоступен), то все попытки его использования делают программу неработоспособной. К счастью, с этого момента начинаются хорошие новости. Копирующее присваивание: ок? деструктор: ок? Да, неявно сгенерированный оператор копирующего присваивания и деструктор будут работоспособны, а именно ~ будут вызывать (а в случае деструктора перекрывать) соответствующие функции базового класса. Так что хорошая новость в том, что хоть что-то работает правильно. Однако не все еще хорошо в .этой семье. В конце концов, в каждом домашнем хозяйстве должен быть минимальный порядок. Можем ли мы найти способ поддержания мира в этой семье? Не хочешь -- заставим! Имеется ли способ в контексте этого примера, при помощи которого автор класса Count мог бы заставить автора производного класса программировать в соответствии с указанными правилами - т.е. чтобы сообщение об ошибке вьщавалось во время компиляции (предпочтительно) или хотя бы во время выполнения профаммы, если автор производного класса наруншл указанное в комментарии к классу Count требование? Идея состоит не в том, чтобы запретить неявное объявление (мы не в состоянии это сделать), а в том, чтобы сделать неявное определение некорректным, чтобы ком-пилятор мог вразумительно сообщить о возникшей ошибке. Вопрос в общем виде: имеется ли способ, при помощи которого автор базового класса может заставить автора производного класса явным образом написать каждую из четырех перечисленных выше базовых операций? Если да, то как? Если нет, то почему? Все время, пока мы знакомились с неявным объявлением и определением четырех базовых операций, мы наталкивались на слова недоступный и неоднозначный . Оказывается, что добавление неоднозначных перегрузок, даже с различными спецификаторами доступа, нам не сильно поможет. Трудно добиться чего-то лучшего, чем то, что мы получили, сделав функции базового класса выборочно недоступными, объявляя их закрытыми (определены ли они реально - вопрос второй) - и этот подход работает для всех функций, кроме одной. Пример 19-4: попытка заставить производный класс не использовать неявно сгенерированные функции, делая функции базового класса недоступными class Base { public: vi rtual -BaseO; private: BaseC const Base& ); не определена Base& operator=( const Base& ); не определена Этот класс Base не имеет конструктора по умолчанию (поскольку объявлен, хотя и НС определен, пользовательский конструктор), и имеет скрытые копирующий конструктор и копирующий оператор присваивания. Нет никакого способа скрыть деструктор.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |