Как именно ведет себя справочная typedef? - PullRequest
5 голосов
/ 13 ноября 2011

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

typedef value_type& reference;
typedef const value_type& const_reference;

Какова же семантика этих типов?

Из того, что я понимаю, они должны "вести себя как ссылки на тип значения", но что именно это означает?

MSDN говорит, что reference это:

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

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

Ответы [ 3 ]

4 голосов
/ 13 ноября 2011

Я думаю, что часть вопроса исходит из предположения, что распределители полезны. Распределители (по крайней мере до 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, а не int&. Это может быть несоответствие типов. Необходимо изменить способ работы базового языка со ссылками, чтобы сделать распределители действительно полезными.

typedef reference предназначен для возврата того, что эквивалентно T& для рассматриваемого распределителя. На современных архитектурах это, вероятно, T&. Однако предполагалось, что на некоторых архитектурах это может быть что-то другое (например, компилятору, ориентированному на архитектуру с указателями «ближний» и «дальний»), может потребоваться специальный синтаксис для ссылок «ближний» и «дальний»). К сожалению, эта блестящая идея оказалась менее чем блестящей. C ++ 11 вносит существенные изменения в распределители - добавляя выделенные области - и модель памяти. Я должен признать, что не знаю достаточно об изменениях в C ++ 11 w.r.t. Распределители, чтобы сказать, становится ли все лучше или хуже.


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

2 голосов
/ 13 ноября 2011

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

enter image description here

Он также определяет reference как определение типа value_type&, которое является определением типа T

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

1 голос
/ 13 ноября 2011

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


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

Причина, по которой существует reference, заключается в том, чтобы предотвратить такие вещи:

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

  typedef T& reference;
  reference baz();
}

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

Он также делает интерфейс чище и позволяет использовать SFINAE:

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

template<typename T>
T& second(T& 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
...