Программирование >>  Полиморфизм без виртуальных функций в с++ 

1 ... 25 26 27 [ 28 ] 29 30 31 ... 144


Однако я сделал одну серьезную ошибку, разрешив инициализировать неконстантную (без спецификатора 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 не может изменять данные, на которые указывает р */ /* ... */



1 ... 25 26 27 [ 28 ] 29 30 31 ... 144

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика