Анимация
JavaScript
|
Главная Библионтека присваивания (и единственный операнд перегруженной унарной операции присваивания) должен быть ссылкой. • Все другие операции могут иметь операнды как типа lvalue, так и rvalue. Используйте ссылку на объект типа const для всех операндов. (Вы могли бы передавать операторы по значению, но обычно это менее эффективно). • Имена переменных составного типа (массивов) создают типы rvalue - временные переменные типа указателя на первый элемент, после инициализации на него и указывающие. Заметьте, что неверно представление о том, что вы не можете инкрементировать имя массива из-за того, что оно является константой. Вы не можете инкрементировать имя массива, потому что оно имеет тип rvalue, а все операции инкремента требуют операндов типа lvalue. • Имена переменных несоставного типа дают lvalue. • Операции *, -> и [] генерируют lvalue, когда относятся к несоставной переменной, иначе они работают подобно именам составных переменных. Если y не является массивом, то x->y создает тип lvalue, который ссылается на этого поле данных. Если y - массив, то x->y генерирует тип rvalue, который ссылается на первую ячейку этого массива. В Си++ перегруженные * и [] должны возвращать ссылки на указанный объект. Операция operator-> таинственна. Правила по существу заставляют вас использовать ее таким же образом, как вы делали бы это в Си. Операция -> рассматривается как унарная с операндом слева от нее. Перегруженная функция должна возвращать указатель на что-нибудь, имеющее поля - структуру, класс или объединение. Компилятор будет затем использовать такое поле для получения lvalue или rvalue. Вы не можете перегрузить . (точку). • Все другие операнды генерируют тип rvalue. Эквивалентные перегруженные операции должны возвращать объекты, а не ссылки или указатели. 149. Перегруженной бинарной операции лучше всего быть встроенным (inline) псевдонимом операции приведения типа Это правило относится к числу тех, которые будут изменены с улучшением качества компиляторов. Рассмотрим следующее, простое для понимания дополнение к классу string из листинга 7 на странице 155: class string enum special { special }; string( special ) {}; ничего не делает. ... public: const string operator+( const string &r ) const; ... ------------------------------------------------------------const string:: operator+( const string &r ) const string tmp( special ); создать пустой объект tmp.buf = new char[ strlen(buf) + strlen(r.buf)+1]; strcpy( tmp.buf, buf ); strcat( tmp.buf, r.buf ); return tmp; Многие компиляторы, получив вышеуказанное, генерируют довольно неэффективный код. Объект tmp должен инициализироваться при вызове конструктора; здесь это не очень дорого, но обычно это ведет к значительно большим расходам. Конструктор копии должен быть вызван для выполнения оператора return, и сам объект также должен быть уничтожен. Иногда вы можете улучшить такое поведение путем перегрузки встроенного псевдонима для операции приведения типа: class string string(const char *left, const char *right ); public: const string string:: operator+( const string &r ) const ; ----------------------------------------------------------- string::string(const char *left, const char *right ) buf = new char[ strlen(left) + strlen(right)+1]; strcpy( buf, left ); strcat( buf, right ); -----------------------------------------------------------inline const string:: operator+( const string &r ) const return string(buf, r.buf); Более эффективные компиляторы здесь на самом деле рассматривают следующее: string s1, s2; s1 + s2; как если бы вы сказали следующее (вы не можете сделать этого сами, потому что buf является закрытым): string(s1.buf, s2.buf) Полезный результат заключается в устранении неявного вызова конструктора копии в операторе return в первом варианте реализации. 150. Не теряйте разум с операторами преобразования типов 151. Если можно, то делайте все преобразования типов с помощью конструкторов Распространенной ошибкой среди начинающих программистов на Си++ является сумасбродство с преобразованием типов. Вы чувствуете, что должны обеспечить преобразование каждого системного типа в ваш новый класс и обратно. Это может привести к подобному коду: class riches богачи public: riches( const rags &r ); class rags оборванцы public: operator riches( void ); Проблема заключается в том, что обе функции определяют преобразование из rags в riches. Следующий код генерирует " постоянную ошибку" (которая прерывает компиляцию), потому что компилятор не знает, использовать ли ему для преобразования rags в riches конструктор в классе riches, или перегруженную операцию в классе rags; конструктор и перегруженная операция утверждают, что выполнят эту работу: rags horatio alger; Гораций Алгер riches bill gates = (riches) horatio alger; Бил Гейтс Эта проблема обычно не так очевидна. Например, если вы определите 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 |