Как именно ведет себя ‘ссылочный’ typedef?

#c #stl #reference #typedef

#c #stl #ссылка #typedef

Вопрос:

Контейнеры STL имеют reference и const_reference typedef , которые, как я видел во многих случаях (контейнеры bool являются единственными исключениями, которые я могу придумать), могут быть тривиально определены как

 typedef value_typeamp; reference;
typedef const value_typeamp; const_reference;
  

Однако, какова именно семантика этих типов?

Насколько я понимаю, они должны «вести себя как ссылки на тип значения», но что именно это означает?

MSDN утверждает, что reference это:

Тип, который предоставляет ссылку на элемент, хранящийся в векторе.

Но что именно это означает? Нужно ли им перегружать определенные операторы или иметь определенное поведение? Если да, то какое поведение требуется?

Комментарии:

1. Я не думаю, что кто-нибудь понимает этот вопрос. Он спрашивает, как должен вести себя тип, который называется «reference». Не ссылочный тип C (например intamp; ).

2. Если подумать, я не думаю, что сейчас вообще понимаю Q. Тип reference or const_reference ведет себя как обычная ссылка или const reference, почему вы думаете, что он должен вести себя по-другому?

3. @Als: Вопрос, в буквальном смысле, таков: что означает «ведет себя как обычная ссылка»? Само по себе это расплывчатое предложение, поэтому я не знаю, каким условиям мне нужно было бы удовлетворять, прежде чем мой тип «будет вести себя как обычная ссылка» (например, если бы я создавал контейнер).

4. Это существенно сократило бы ваш реальный вопрос до Что такое ссылка? Какие операции он должен поддерживать и какие операции поддерживает ссылка, предоставляемая через мой интерфейс? На самом деле это вообще не имеет отношения к стандартным библиотечным контейнерам.

Ответ №1:

Я думаю, что часть вопроса исходит из предположения, что распределители полезны. Распределители (по крайней мере, до C 11) были чем-то вроде позднего дополнения к STL:

Люди хотели, чтобы контейнеры не зависели от модели памяти, что было несколько чрезмерно, поскольку язык не включает модели памяти. Люди хотели, чтобы библиотека предоставляла некоторый механизм для абстрагирования моделей памяти. Более ранние версии STL предполагали, что размер контейнера можно выразить как целое число типа size_t и что расстояние между двумя итераторами имеет тип ptrdiff_t . И теперь нам сказали, почему бы вам не абстрагироваться от этого? Это сложная задача, потому что язык не абстрагируется от этого; Массивы C и C не параметризуются этими типами. Мы изобрели механизм под названием «распределитель», который инкапсулирует информацию о модели памяти. Это вызвало серьезные последствия для каждого компонента в библиотеке. Вы можете задаться вопросом, какое отношение модели памяти имеют к алгоритмам или интерфейсам контейнера. Если вы не можете использовать такие вещи, как size_t , вы также не можете использовать такие вещи, как T* из-за разных типов указателей ( T* , T huge * и т.д.). Тогда вы не можете использовать ссылки, потому что с разными моделями памяти у вас разные ссылочные типы. Это имело огромные последствия для библиотеки.

К сожалению, они оказались нестандартными:

Я изобрел распределители для работы с архитектурой памяти Intel. Теоретически это не такая уж плохая идея — иметь слой, который инкапсулирует все содержимое памяти: указатели, ссылки, ptrdiff_t , size_t . К сожалению, они не могут работать на практике. Например,

 vector<int, alloc1> a(...);
vector<int, alloc2> b(...);
  

вы не можете сейчас сказать:

 find(a.begin(), a.end(), b[1]);
  

b[1] возвращает alloc2::reference а не intamp; . Это может быть несоответствие типов. Необходимо изменить способ, которым основной язык обрабатывает ссылки, чтобы сделать распределители действительно полезными.

reference Typedef предназначен для возврата любого эквивалента Tamp; для рассматриваемого распределителя. В современных архитектурах это, вероятно, Tamp; . Однако предполагалось, что на некоторых архитектурах это может быть что-то другое (например, компилятору, ориентирующемуся на архитектуру с указателями «near» и «far», может потребоваться специальный синтаксис для ссылок «near» и «far»). К сожалению, эта блестящая идея оказалась менее чем блестящей. C 11 вносит существенные изменения в распределители — добавляя распределители с ограниченной областью действия — и модель памяти. Я должен признать, что я недостаточно знаю об изменениях C 11 в отношении распределителей, чтобы сказать, станет ли все лучше или хуже.


Глядя на комментарии к исходному вопросу, поскольку в Стандарте фактически не указано, как должны быть реализованы контейнеры (хотя Стандарт предъявляет так много требований к поведению контейнеров, что это могло бы также …), какой бы тип вы ни ввели в качестве reference определения, он должен иметь поведение Tamp; , на которое кто-то потенциально мог бы положиться при реализации контейнера: объект типа reference должен быть прозрачным псевдонимом для исходного объекта, присвоение ему должно изменять значение исходного объекта без нарезки, нет необходимости поддерживать «переустановка» reference , взятие адреса reference должно возвращать адрес исходного объекта и т.д. Если это вообще возможно, то на самом деле так и должно быть Tamp; ; и единственный случай, который я могу представить, когда это было бы невозможно, был бы, если бы вы выделяли память, которой вы не могли бы манипулировать с помощью указателей или ссылок (например, если бы «memory» на самом деле была на диске, или если бы память была фактически выделена на отдельном компьютере и доступна по сети через вызовы RPC и т.д.).

Комментарии:

1. Похоже, это для STL, а не STD.

2. «единственный случай, который я могу представить, когда это было бы невозможно, это если бы вы выделяли память, которой вы не могли бы манипулировать с помощью указателей или ссылок» Стандарт в любом случае не позволил бы вам создавать распределители с этим, потому что вы не можете подделать ссылку в C . Вы не можете указать семантику ссылки на объект. Вот почему vector<bool> технически это не стандартный контейнер. Он не соответствует правилам, установленным для std::vector экземпляров. И это не так, потому что вы должны быть в состоянии сделать amp;vec[3] , что невозможно с vector<bool> .

3. @Pubby: STL существовал до стандарта и был существенно изменен, когда он стал частью стандарта (одно изменение заключается в том, что 2/3 алгоритмов, которые изначально были у Степанова, были удалены). По словам Степанова, C был некоторым образом изменен, когда STL был принят в стандарт (одним из изменений является добавление функторов). Мои комментарии в последнем абзаце ответа, я полагаю, только «правильные» для C -98. C -03 изменил правила таким образом, что реализациям разрешено предполагать, что reference на самом деле Tamp; (как указано в вашем ответе, совершенно ясно).

Ответ №2:

Взято из стандарта:

введите описание изображения здесь

Он также определяется reference как typedef value_typeamp; , который является typedef из T

(Новый ответ, поскольку предыдущий был другого фокуса)

Ответ №3:

Вы спрашиваете: «Могу ли я всегда разыменовывать ссылку? И да, вы можете. Это означает, что разыменованный reference может делать все, что разыменованный value_typeamp; может делать, то есть все, что value_type может сделать. Если это имеет смысл для вас.


Вы не можете перегружать операторы в typedefs. typedefs имеют то же поведение, что и тип, которому они назначены. Причина, по которой они определены как typedef, заключается в том, чтобы сделать их менее громоздкими и предоставить общий «интерфейс»

reference Существует причина для предотвращения подобных вещей:

 template<typename T>
struct foo {
  Tamp; bar();

  typedef Tamp; reference;
  reference baz();
}

foo<int> x;
foo<int>::Tamp; y = x.bar(); // error! returns a Tamp; but I can't create one!
foo<int>::reference z = x.baz(); // ok! 
  

Это также делает интерфейс более чистым и позволяет использовать SFINAE:

 template<typename T>
typename T::reference second(T::referenceamp; t) { return t.at(1); };

template<typename T>
Tamp; second(Tamp; t) { return t[1]; };

std::vector v(10);
foo f(10); // type with [] overloaded but no reference typedef
second(x) = 5; // calls first def
second(f) = 3; // calls second def
  

Комментарии:

1. Извините, но я думаю, вы пропустили вопрос. Я спрашивал, какие операторы / функции должен поддерживать reference тип… не могу ли я перегрузить то, что там есть… Я уже знаю, как typedef работает и почему они ввели абстракцию и т.д.; Я просто хотел бы знать, чему reference должен удовлетворять стандарт.

2. @Mehrdad reference может делать все, что value_typeamp; может сделать, то есть все, что value_type может сделать.

3. Тогда какой смысл использовать, reference когда вы можете просто использовать value_typeamp; , если это одно и то же? Должен существовать набор необходимых и достаточных условий, которым reference должны удовлетворять s, что, безусловно, не является «всем»…

4. @Mehrdad Это добавляет описательности. Что, если я случайно написал, value_type когда собирался писать value_typeamp; ? Подобные ошибки очень трудно найти, но их можно полностью избежать, если я буду использовать reference .

5. «Должен существовать набор необходимых и достаточных условий, которым должны удовлетворять ссылки, что, безусловно, не является «всем» …» Почему? Первоначальное намерение reference состояло в том, чтобы быть чем-то, что могло быть типом «конвертируемым в». Но стандартные контейнеры этого не допускают. Следовательно, reference должно быть не больше и не меньше, чем value_typeamp; . Не все, что существует в C , должно иметь причину. Иногда это просто что-то, что сработало определенным образом.