Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 [ 37 ] 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85

Задача 19. Не можешь - научим,

не хочешь - заставим! (или как заставить

потомков вести себя прилично) Сложность: 5

Оказывается, иногда вы можете уберечь программистов производных классов от некоторых простых ошибок. Эта задача - о безопасном дизайне базовых классов, который не позволяет разработчикам производных классов пойти неверным путем.

Вопрос для новичка

1. В каком случае происходит неявное объявление и определение перечисленных ниже функций? С какой семантикой? Будьте точны и опишите условия, при которых их неявно определенные версии делают программы некорректными:

а) конструктор по умолчанию;

б) копирующий конструктор;

в) копирующий оператор присваивания;

г) деструктор.

2. Какие функции неявно объявлены и созданы компилятором в следующем классе х? С какими сигнатурами?

class X {

auto ptr<int> i ;

Вопрос для профессионала

3. Пусгь у вас есть базовый класс, который требует, чтобы все производные классы НС использовали ни одной неявно объявленной и созданной компилятором функции. Например;

class Count { public:

Этим комментарием автор класса Count документирует, что производные классы должны наследоваться виртуально, и что их конструкторы должны вызывать конструктор класса Count специального назначения.

Count( /* Специальные параметры */ );

Count& operator=( const Count& ); обычный

virtual -CountO ; Обычный

К сожалению, программистам, как и всем остальным людям, свойственно ошибаться, так что иногда они будут забывать, что они обязаны явно написать две функции.

class BadDerived : private vi rtual Count { int i ;

Конструктор no умолчанию: должен вызывать специальный конструктор, но делает ли он это?

копирующий конструктор: должен вызывать специальный конструктор, но делает ли он это?

копирующее присваивание: ок? деструктор: ок?



Имеется ли способ в контексте этого примера, при помощи которого автор класса count мог бы заставить автора производного класса программировать в соответствии с указанными правилами - т.е. чтобы сообщение об ошибке выдавалось во время компиляции (предпочтительно) или хотя бы во время выполнения программы, если автор производного класса нарушил указанное в комментарии к классу Count требование?

Вопрос в общем виде: имеется ли способ, при помощи которого автор базового класса может заставить автора производного класса явным образом написать каждую из четырех перечисленных выше базовых операций? Если да, то как? Если нет, то почему?

Решение

Неявно генерируемые функции

В С++ компилятор может неявно генерировать четыре функции-члена класса: конструктор по умолчанию, копирующий конструктор, оператор копирующего присваивания и деструктор.

Причина этого заключается в сочетании удобства и обратной совместимости с С. Вспомним, что структуры в стиле С struct - это просто классы, содержащие только открытые члены-данные; в частности, они не должен иметь (явно определенных) функций-членов, и все же должен существовать способ создавать, копировать и уничтожать их. Для этого С++ автоматически генерирует соответствующие функции (или некоторое их подмножество) для выполнения указанных действий, если вы не определили их самостоятельно.

В задаче спрашивается о том, что именно означает слово "соответствующие".

1. В каком случае происходит неявное объявление и определение перечисленных ниже функций? С какой семантикой? Будьте точны и опишите условия, при которых их неявно определенные версии делают программы некорректными.

Говоря кратко, неявно объявленная функция становится неявно определенной только при попытке се использовать. Например, неявно объявленный конструктор по умолчанию неявно создается компилятором только тогда, когда вы пытаетесь создать объект без указания параметров конструктора.

Почему следует различать случаи неявного объявления и неявного определения функции? Потому что вполне реальна ситуация, когда функция никогда не вызывается, и если это так, то программа остается корректной даже если неявное определение функции некорректно.

Для удобства в этой задаче, если явно не оговорено иное, "член" означает "нестатический член-данные класса". Кроме того, я буду говорить "неявно сгенерированные", подразумевая "неявно объявленные и определенные".

Спецификации исключений неявно определенных функций

Во всех четырех случаях неявного объявления функций компилятор делает их спецификации исключений достаточно свободными, допускающими генерацию любых исключений неявно определенными функциями. Например:

пример 19-1(а)

class с ... {

...



Поскольку явно объявленных конструкторов нет, семантика неявно сгенерированного конструктора по умолчанию заключается в вызове конструкторов по умолчанию базовых классов и членов. Следовательно, спецификация исключений неявно сгенс-рированного конструктора класса с должна позволять генерацию любых исключений, которые могут быть сгенерированы конструкторами по умолчанию базовых классов или членов. Если хоть один базовый класс с или его член имеет конструктор по умолчанию без явной спецификации исключений, то неявно объявленный конструктор по умолчанию с может генерировать любое исключение:

public:

inline С::С(); может генерировать что угодно

Если все базовые классы и члены с имеют конструкторы по умолчанию с явно указанными спецификациями исключений, то неявно объявленный конструктор С может генерировать исключения любого из типов, упомянутых в этих спецификациях исключений:

public:

inline с::С() throw (

все, что могут генерировать конструкторы по умолчанию базовых классов или членов; т.е. здесь находится объединение всех типов, упомянутых в спецификациях исключений конструкторов по умолчанию базовых классов и членов с );

Оказывается, здесь есть потенциальная ловушка. Что, если одна из неявно сгенерированных функций перекрывает унаследованную виртуальную функцию? Этого не может произойти для конструкторов (поскольку конструкторы НС бывают виртуальными), но вполне возможно для оператора копирующего присваивания (если вы сделаете базовую версию соответствующей сигнатуре версии, неявно сгенерированной в производном классе), и то же может произойти и для деструктора.

пример 19-1(6): опасный момент!

class Derived; class Base { public:

несколько искусственный пример, но технически вполне

возможно использовать этот метод для объявления

оператора присваивания Base, который получает в

качестве аргумента Derived. Перед тем как даже просто

подумать применить такой метод, обязательно прочтите

подраздел 33 в [Meyers96].

vi rtual Bases /* или Derived* */ operator=( const Derived* ) throwC Bl ); vi rtual -BaseC) throwC в2 );

class Member { publiс:

Members operator=C const метЬег& ) throwC Ml ) ; -MemberC) throwC М2 );

class Derived : public Base {

Member m ;

неявно объявлены четыре функции: Derived::Derived(); ok

Derived::Oerived( const Derived* ); ok Derived& Derived::operator =

(const Derived*) throw(Bl,Ml); Ошибка



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 [ 37 ] 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85