Анимация
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

Есть серьезные причины не делать показанное выше. Во-первых, это не будет работать после наследования. Если вы определяете:

class derived : public some class

public:

~derived();

Предположим, что генерированная компилятором операция operator=() выполнится за операцией operator=() базового класса.

Вследствие того, что деструктор базового класса определен (правильно) как виртуальный, обращение предыдущего базового класса к:

this->~some class()

вызывает деструктор производного класса, поэтому вы уничтожите значительно больше, чем намеревались. Вы можете попытаться исправить эту проблему, изменив вызов деструктора на:

this->some class::~some class();

Явное упоминание имени класса - some class:: в этом примере - подавляет механизм виртуальной функции. Функция вызывается, как если бы она не была виртуальной.

Деструктор не является единственной проблемой. Рассмотрим простое присваивание объектов производного класса:

derived d1, d2;

d1 = d2;

Операция производного класса operator=() (вне зависимости от того, генерируется она компилятором или нет) образует цепочку с operator=() базового класса, который в настоящем случае использует оператор new() для явного вызова конструктора базового класса. Конструктор, тем не менее, делает значительно больше, чем вы можете видеть в определении. В частности, он инициализирует указатель таблицы виртуальных функций так, чтобы он указывал на таблицу его класса. В

const some class &operator=( const some class &r ) {

if( this != &r )

this->~some class(); this->some class::some class( r );

Тем не менее, такое поведение является нестандартным.



текущем примере перед присваиванием указатель vtable указывает на таблицу производного класса. После присваивания указатель vtable указывает на таблицу базового класса; он был переинициализирован неявным вызовом конструктора при вызове new в перегруженной операции operator=() .

Таким образом, вызовы конструкторов в операции operator=() просто не будут работать, если есть таблица виртуальных функций. Так как вы можете знать или не знать, на что похожи определения вашего базового класса, то вы должны исходить из того, что таблица виртуальных функций имеется, и поэтому не вызывайте конструкторов.

Лучшим способом устранения дублирования кода в операции присваивания operator=() является использование простой вспомогательной функции:

class some class

void create ( void );

void create ( const some class &r ); void destroy ( void );

public:

virtual

~some class( void ) { destroy(); } some class( void ) { create(); }

const some class &operator=( const some class &r );

inline const some class &some class::operator=( const

some class &r )

destroy(); create( r );

inline some class::some class( void )

create(); ~some class::some class( void )

destroy();



Часть 8е. Перегрузка операций

145. Операция - это сокращение (без сюрпризов)

Операция - это не произвольный значок, означающий все, что вы ни пожелаете. Это аббревиатура англоязычного слова. Например, символ + значит "прибавить", поэтому вы не должны заставлять перегруженный operator+() делать что-нибудь еще. Хотя здесь все ясно (вы можете определить a+b для вычитания b из a, но не должны делать этого), я на самом деле веду речь о проблемах более творческого характера.

Вы можете благоразумно доказывать, что, когда выполняете конкатенацию, то "прибавляете" одну строку к концу другой, поэтому перегрузка + для конкатенации может быть приемлема. Вы также можете доказывать, что разумно использовать операции сравнения для лексикографического упорядочивания в классе string, поэтому перегрузка операций < , == и т.д. также вероятно пойдет. Вы не сможете аргументировано доказать, что - или * имеют какой-нибудь смысл по отношению к строкам.

Другим хорошим примером того, как нельзя действовать, является интерфейс Си++ iostream. Использование сдвига (<<) для обозначения " вывод" является нелепым. Ваши функции вывода в Си назывались printf() , а не shiftf() . Я понимаю, что Страуструп выбрал сдвиг, потому что он сходен с механизмом перенаправления ввода/вывода различных оболочек UNIX, но этот довод на самом деле не выдерживает проверки. Страуструп исходил из того, что все программисты на Си++ понимают перенаправление в стиле UNIX, но эта концепция отсутствует в некоторых операционных системах - например, в Microsoft Windows. К тому же, для того, чтобы аналогия была полной, операция > должна быть перегружена для выполнения операции затирания, а >> - добавления в конец. Тем не менее, тот факт, что > и >> имеют различный приоритет, делает реализацию такого поведения затруднительной. Дело осложняется тем, что операторы сдвига имеют неправильный уровень приоритета. Оператор типа cout <<x+=1 не будет работать так, как вы ожидаете, потому что у << более высокий приоритет, чем у +=, поэтому оператор интерпретируется как (cout << x) += 1, что неверно. Си++ нуждается в расширяемости, обеспечиваемой системой iostream, но он вынужден добиваться ее за счет введения операторов "ввода" и "вывода", имеющих низший приоритет по отношению к любому оператору языка.



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