|
Программирование >> Полиморфизм без виртуальных функций в с++
Однако я сделал одну серьезную ошибку, разрешив инициализировать неконстантную (без спецификатора const) ссылку значением, не являющимся lvalue. Например: void incr(int& rr) { rr++; } void g() { double ss = 1; incr(ss); примечание: передано double, ожидалось int исправлено в версии 2.0 Из-за различия типов int& не может ссылаться на переданное значение типа double, поэтому генерировалась временная переменная для хранения значения типа int, инициализированная значением ss. В связи с этим функция incr модифицировала данную временную переменную, а вызывающая функция получала неизмененное значение. Я разрешил инициализировать ссылки не-lvalue, чтобы сделать различие между вызовом по значению и по ссылке деталью реализации вызываемой функции, которая вызывающей вообще неинтересна. Для константных ссылок это возможно, для неконстантных - нет. В версии 2.0 определение С-и- было соответствующим образом изменено. Следует отметить, что константную ссылку можно инициализировать не-lvalue, атакже lvalue, для типа которой требуется преобразование. В частности, это позволяет вызывать написанную на Fortran функцию с аргументом-константой: extern Fortran float sqrt(const float&); void f() { sqrt(2); вызов с передачей аргумента по ссылке Помимо очевидных применений ссылок (для передачи аргументов), мы считали важной возможность использовать ссылки как возвращаемые значения. Это позволило легко написать оператор взятия индекса для класса String: class String { ... char& operator[](int index); оператор взятия индекса возвращает ссылку void f(Strings s, int i) { char cl = s[i]; присвоить результат operator[] s[i] = cl; присвоить результату operator[] ... Возврат ссылки на внутреннее представление String предполагает, что полызо-ватели ведут себя ответственно. 3.7.1. Lvalue и Rvalue Если operator [ ] {) реализован так, что возвращает ссылку, то не удастся приписать различную семантику чтению и записи элемента, извлеченного по индексу. Например, в выражении Sl[i] = S2[j]; МЫ не можем одновременно произвести действие над строками sl и s2, где в одном случае значение присваивается, а в другом - читается. Размышляя над задачами реализации строк с разделяемым представлением и доступа к базам данных, мы с Джонатаном Шопиро пришли к выводу о необходимости иметь различную семантику для доступа на чтение и на запись. В обоих случаях чтение - это простая и дешевая операция, тогда как запись - потенциально дорогая и сложная, и для нее может потребоваться копирование структур данных. Рассматривались две возможности: □ определить различные функции для применения к lvalue и rvalue; □ заставить программиста использовать вспомогательную структуру данных. В результате был выбран второй подход, поскольку он помогал избежать расширения языка, а кроме того, мы считали возврат объекта, описывающего положение в контейнерном классе вроде String, более общим приемом. Основная идея заключалась в том, чтобы иметь вспо.могательный класс, который определяет позицию в контейнерном классе почти как ссылку, но имеет разную семантику для чтения и записи. Например: class char ref { идентификация символа в строке friend class String; int i; String* s; char ref(String* ss, int ii); { s=ss; i=ii; } public: void operator= (cliar c) ; operator cliar () ; Присваивание объекту типа char ref реализовано как присваивание символу, на который он ссылается, а чтение объекта типа char ref - как преобразование в тип char, возвращающее значение символа: void cliar ref: :operator= (char с) { s->r[i]=c; ) char ref::operator char() { return s->r[i]; } Заметим, что только String может создать char ref. Фактическое присваивание реализуется классом String: class String { friend class char ref; char* r; public: char ref operator[](int i) { return char ref(this,i); } ... При наличии таких определений предложение sl[i] = s2[j]; означает si.operator[] (i) = s2.operator[] (j ) где si .operator [] (i) и s2 . operator [ ] (j) возвращают временные объекты класса char ref. Это в свою очередь означает si.operator[](i).operator=(s2.operator[](j).operator charO) Встраивание во многих случаях делает производительность приема вполне приемлемой, а использование дружественных отнощений для ограничения создания объектов типа char ref гарантирует, что мы не получим проблем с долго-живущими временными объектами (см. раздел 6.3.2). Данный прием был использован в классе String, доказавшем свою полезность. Однако для таких простых случаев, как доступ к отдельным символам, он выглядит усложненным и тяжеловесным. Поэтому я искал альтернативные решения. Одна такая возможность - составные операторы (см. раздел 11.6.3). 3.8. Константы в операционных системах доступ к некоторой области памяти часто прямо или косвенно контролируется двумя битами: первый дает информацию о том, можно ли пользователю писать в эту область, а второй - можно ли из нее читать. Эта идея показалась мне имеющей прямое отношение к С++, и я подумывал о том, чтобы разрешить для любого типа задавать атрибут readonly или writeonly. Rot как излагается эта идея во внутренней записке, датированной январем 1981 г. [Stroustrup,1981b]: До настоящего времени в С было невозможно укозоть, что некоторый элемент донных должен быть доступен только для чтения, то есть его зночение неизменно. Не было также способа наложить ограничения но то, что функция может делать с оргументоми. Деннис Ричи отметил, что если бы существовал модификатор типа readonly, то легко можно было получить обе возможности: readonly char table[1024]; /* символы в таблице table нельзя изменять */ int f(readonly int * p) { /* f не может изменять данные, на которые указывает р */ /* ... */
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |