Категории значений и ссылки на нихValue categories, and references to them

В этом разделе описываются различные категории значений (и ссылки на значения), которые существуют в C++.This topic describes the various categories of values (and references to values) that exist in C++. Вы, несомненно, слышали о значениях lvalue и rvalue, но могли не рассматривать их с точки зрения, изложенной в этом разделе.You will doubtless have heard of lvalues and rvalues, but you may not think of them in the terms that this topic presents. Существуют и другие виды значений.And there are other kinds of values, too.

Каждое выражение в C++ возвращает значение, которое принадлежит к одной из категорий, описанных в этом разделе.Every expression in C++ yields a value that belongs to one of the categories discussed in this topic. Существуют аспекты языка C++, которые они упрощают, и правила, требующие должного понимания этих категорий значений и ссылок на них.There are aspects of the C++ language, its facilies, and rules, that demand a proper understanding of these value categories, and references to them. Например, получение адреса значения, копирование значения, перенос значения и пересылка значение в другую функцию.For example, taking the address of a value, copying a value, moving a value, and forwarding a value on to another function. В этом разделе не приводится подробное описание всех этих аспектов, но он содержит базовые сведения для ясного понимания принципа их работы.This topic doesn't go into all of those aspects in depth, but it provides foundational information for a solid understanding of them.

Сведения в этом разделе представлены с точки зрения анализа категорий значений на основе двух независимых свойств идентификации и перемещаемости, предложенного Бьерном Страуструпом [Stroustrup, 2013].The info in this topic is framed in terms of Stroustrup's analysis of value categories by the two independent properties of identity and movability [Stroustrup, 2013].

У lvalue есть идентификаторAn lvalue has identity

Что означает, что у значения есть идентификатор?What does it mean for a value to have identity? Если у вас есть (или его можно выделить) адрес памяти значения и вы можете его безопасно использовать, то у значения есть идентификатор.If you have (or you can take) the memory address of a value and use it safely, then the value has identity. Таким образом вы не просто сравниваете содержимое значений: вы можете сравнивать или различать их по идентификатору.That way, you can do more than compare the contents of values: you can compare or distinguish them by identity.

У lvalue есть идентификатор.An lvalue has identity. Сейчас этот вопрос представляет лишь исторический интерес, но "l" в имени lvalue представляет собой сокращение от "left" (слева) (то есть левая сторона присваивания).It's now a matter of only historical interest that the "l" in "lvalue" is an abbreviation of "left" (as in, the left-hand-side of an assignment). В C++ lvalue может отображаться в левой или правой части присваивания.In C++, an lvalue can appear on the left or on the right of an assignment. То есть "l" в имени lvalue уже не помогает понять, что это за значения.The "l" in "lvalues", then, doesn't actually help you to comprehend nor define what they are. Необходимо только понимать, что lvalue — это значение, которое имеет идентификатор.You need only to understand that what we call an lvalue is a value that has identity.

Примеры выражений с lvalue: именованная переменная или константа; или функция, возвращающая ссылку.Examples of expressions that are lvalues include: a named variable or constant; or a function that returns a reference. Примеры выражений, которые не содержат lvalue: временное значение или функция, возвращающая результат по значению.Examples of expressions that are not lvalues include: a temporary; or a function that returns by value.

int& get_by_ref() { ... }
int get_by_val() { ... }

int main()
{
    std::vector<byte> vec{ 99, 98, 97 };
    std::vector<byte>* addr1{ &vec }; // ok: vec is an lvalue.
    int* addr2{ &get_by_ref() }; // ok: get_by_ref() is an lvalue.

    int* addr3{ &(get_by_ref() + 1) }; // Error: get_by_ref() + 1 is not an lvalue.
    int* addr4{ &get_by_val() }; // Error: get_by_val() is not an lvalue.
}

Как мы сказали, у значений lvalue имеются идентификаторы, но это верно и для значений xvalue.Now, while it's a true statement that lvalues have identity, so do xvalues. Мы подробнее остановимся на значениях xvalue позже в этом разделе.We'll go more into what an xvalue is later in this topic. Пока просто запомните, что существует категория значений glvalue, от "generalized lvalue" (универсальное значение lvalue).For now, just be aware that there is a value category called glvalue, for "generalized lvalue". Расширенный набор значений glvalue содержит и значения lvalue (также известные как классические значения lvalue), и значения xvalue.The superset of glvalues contains both lvalues (also known as classical lvalues) and xvalues. Поэтому, хотя утверждение, что у lvalue есть идентификатор, и верно, полный набор значений с идентификатором — это набор значений glvalue, как показано на этом рисунке.So, while "an lvalue has identity" is true, the complete set of things that have identity is the set of glvalues, as shown in this illustration.

У lvalue есть идентификатор

Значение rvalue является перемещаемым, а lvalue — нетAn rvalue is movable; an lvalue is not

Но есть значения, которые не относятся к категории glvalue.But there are values that are not glvalues. Следовательно, существуют значения, для которых невозможно получить адрес памяти (либо не гарантируется, что он будет действительным).Consequently, there are values that you can't obtain a memory address for (or you can't rely on it to be valid). Мы видели несколько таких значений в приведенном выше примере кода.We saw some such values in the code example above. Это может показаться недостатком.This sounds like a disadvantage. Но на самом деле преимущество такого значения заключается в том, что вы можете переместить данные из него (что, как правило, не требует больших затрат), а не копировать их (что, как правило, требует несколько больших затрат).But in fact the advantage of a value like that is that you can move from it (which is generally cheap), rather than copy from it (which is generally expensive). Перемещение значения означает, что оно не остается там, где было до этого.Moving from a value means that it's no longer in the place it used to be. Поэтому следует избегать попыток обращения к переменным там, откуда они уже были перемещены.So, trying to access it in the place it used to be is something to be avoided. Когда и как следует перемещать переменную выходит за рамки данного раздела.A discussion of when and how to move a value is out of scope for this topic. Для данного раздела нам достаточно знать, что значение, которое может быть перемещено, называется rvalue (или классическое значение rvalue).For this topic, we just need to know that a value that is movable is known as an rvalue (or classical rvalue).

Буква "r" в rvalue представляет собой сокращение от "right" (справа) (то есть правая сторона присвоения).The "r" in "rvalue" is an abbreviation of "right" (as in, the right-hand-side of an assignment). Но значения rvalue, а также ссылки на rvalue, можно использовать и за пределами присвоений.But you can use rvalues, and references to rvalues, outside of assignments. Значит, на "r" в имени rvalue можно не обращать особого внимания.The "r" in "rvalues", then, is not the thing to focus on. Необходимо только понимать, что rvalue — это значение, которое можно перемещать.You need only to understand that what we call an rvalue is a value that is movable.

А значение lvalue не является перемещаемым, как показано на этом рисунке.An lvalue, conversely, isn't movable, as shown in this illustration. Перемещенное значение lvalue противоречило бы определению lvalue, и возникла бы непредвиденная проблема для кода, который вполне обоснованно по-прежнему должен иметь доступ к lvalue.An lvalue that moved would defy the definition of lvalue, and it would be an unexpected problem for code that very reasonably expected to be able to continue to access the lvalue.

Значение rvalue является перемещаемым, а lvalue — нет

Невозможно переместить значение lvalue.You can't move an lvalue. Но есть еще значение типа glvalue (набор значений с идентификатором), которое вы можете переместить — если, конечно, вы знаете, что делаете (в то числе достаточно внимательны, чтобы не обращаться к нему после перемещения) — это значение xvalue.But there is a kind of glvalue (the set of things with identity) that you can move—if you know what you're doing (including being careful not to access it after the move)—and that's the xvalue. Мы вернемся к этой идее позже, когда рассмотрим полную картину категорий значений.We'll revisit this idea one more time below, when we look at the complete picture of value categories.

Ссылки на rvalue и правила привязки ссылокRvalue references, and reference-binding rules

В этом разделе описывается синтаксис ссылки на rvalue.This section introduces the syntax for a reference to an rvalue. Нам придется подождать выпуска другого раздела, чтобы углубиться в суть перемещения и пересылки, но сейчас достаточно будет сказать что для решения этих проблем необходимы ссылки на rvalue.We'll have to wait for another topic to go into a substantial treatment of moving and forwarding, but suffice to say that rvalue references are a necessary piece of the solution of those problems. Однако прежде чем мы рассмотрим ссылки на rvalue, необходимо подробнее рассмотреть T&— то, что ранее мы называли просто "ссылкой".Before we look at rvalue references, though, we first need to be clearer about T&—the thing we've formerly been calling just "a reference". Это действительно "ссылка на lvalue" (непостоянная), которая ссылается на значение, которое может записать пользователь ссылки.It's really "an lvalue (non-const) reference", which refers to an value to which the user of the reference can write.

template<typename T> T& get_by_lvalue_ref() { ... } // Get by lvalue (non-const) reference.
template<typename T> void set_by_lvalue_ref(T&) { ... } // Set by lvalue (non-const) reference.

Ссылку на lvalue можно привязать к lvalue, но не к rvalue.An lvalue reference can bind to an lvalue, but not to an rvalue.

Существуют постоянные ссылки на lvalue (T const&), которые ссылаются на объекты, в которые пользователь, использующий ссылку, не может записывать данные (например, константа).Then there are lvalue const references (T const&), which refer to objects to which the user of the reference can't write (for example, a constant).

template<typename T> T const& get_by_lvalue_cref() { ... } // Get by lvalue const reference.
template<typename T> void set_by_lvalue_cref(T const&) { ... } // Set by lvalue const reference.

Постоянную ссылку на lvalue можно привязать к lvalue или rvalue.An lvalue const reference can bind to an lvalue or to an rvalue.

Синтаксис ссылки на rvalue типа T записывается как T&&.The syntax for a reference to an rvalue of type T is written as T&&. Ссылка на rvalue указывает на перемещаемое значение —, содержимое которого не требуется сохранять после использования (например, временное значение).An rvalue reference refers to a movable value—an value whose contents we don't need to preserve after we've used it (for example, a temporary). Так как весь смысл состоит в перемещении (то есть изменении) значения, привязанного к ссылке на rvalue, квалификаторы const и volatile (также известные как CV-квалификаторы) не применяются к ссылкам на rvalue.Since the whole point is to move from (thereby modifying) the value bound to an rvalue reference, const and volatile qualifiers (also known as cv-qualifiers) don't apply to rvalue references.

template<typename T> T&& get_by_rvalue_ref() { ... } // Get by rvalue reference.
struct A { A(A&& other) { ... } }; // A move constructor takes an rvalue reference.

Ссылка на rvalue привязывается к rvalue.An rvalue reference binds to an rvalue. На самом деле, с точки зрения разрешения перегрузки, rvalue предпочтительнее привязывать к ссылке на rvalue, чем к постоянной ссылке на lvalue.In fact, in terms of overload resolution, an rvalue prefers to be bound to an rvalue reference than to an lvalue const reference. Но ссылку на rvalue невозможно привязать к lvalue, ведь, как мы сказали, ссылка на rvalue указывает на значение, содержимое которого не предполагается сохранять (например, параметр для конструктора перемещения).But an rvalue reference can't bind to an lvalue because, as we've said, an rvalue reference refers to a value whose contents it's assumed we don't need to preserve (say, the parameter for a move constructor).

Можно также передать rvalue, где ожидается аргумент по значению, с помощью конструкции копирования (или конструкции перемещения, если значение rvalue — это xvalue).You can also pass an rvalue where a by-value argument is expected, via copy construction (or via move construction, if the rvalue is an xvalue).

У значения glvalue имеется идентификатор, а у значения prvalue — нетA glvalue has identity; a prvalue does not

На этом этапе мы знаем, что такое идентификатор.At this stage, we know what has identity. И мы знаем, что может быть перемещено, а что — нет.And we know what's movable and what isn't. Но мы еще не назвали набор значений, у которых нет идентификатора.But we haven't yet named the set of values that don't have identity. Этот набор называется значениями prvalue, или чистыми значениями rvalue.That set is known as the prvalue, or pure rvalue.

int& get_by_ref() { ... }
int get_by_val() { ... }

int main()
{
    int* addr3{ &(get_by_ref() + 1) }; // Error: get_by_ref() + 1 is a prvalue.
    int* addr4{ &get_by_val() }; // Error: get_by_val() is a prvalue.
}

У значения lvalue имеется идентификатор, а у значения prvalue — нет

Полная картина категорий значенийThe complete picture of value categories

Остается только объединить сведения и рисунки, приведенные выше, в одно большие изображение.It only remains to combine the info and illustrations above into a single, big picture.

Полная картина категорий значений

glvalue (i)glvalue (i)

У glvalue (универсальное значение lvalue) имеется идентификатор.A glvalue (generalized lvalue) has identity.

lvalue (i&!m)lvalue (i&!m)

У lvalue (относится к типу glvalue) имеется идентификатор, но его невозможно перемещать.An lvalue (a kind of glvalue) has identity, but isn't movable. Обычно это доступные для чтения и записи значения, передаваемые с помощью ссылки, постоянной ссылки или значения, если копирование не требует больших затрат.These are typically read-write values that you pass around by reference or by const reference, or by value if copying is cheap. Невозможно привязать значение lvalue к ссылке на rvalue.An lvalue can't be bound to an rvalue reference.

xvalue (i&m)xvalue (i&m)

Значение xvalue (относящееся к типу glvalue, но также являющееся разновидностью rvalue) имеет идентификатор и может быть перемещено.An xvalue (a kind of glvalue, but also a kind of rvalue) has identity, and is also movable. Это может быть прежнее значение lvalue, которое вы решили переместить, так как копирование является ресурсоемким, и вы не допустите обращений к нему после этого.This might be an erstwhile lvalue that you've decided to move because copying is expensive, and you'll be careful not to access it afterward. Вот как значение lvalue можно превратить в значение xvalue.Here's how you can turn an lvalue into an xvalue.

struct A { ... };
A a; // a is an lvalue...
static_cast<A&&>(a); // ...but this expression is an xvalue.

В приведенном выше примере кода мы еще ничего не перемещали.In the code example above, we haven't moved anything yet. Мы только создали значение xvalue путем приведения lvalue к неименованной ссылке на rvalue.We've just created an xvalue by casting an lvalue to an unnamed rvalue reference. Его по-прежнему можно идентифицировать по имени lvalue. А так как это значение xvalue, его можно перемещать.It can still be identified by its lvalue name; but, as an xvalue, it is now capable of being moved. Чтобы узнать причины для этого и изучить подробное описание перемещения, придется дождаться выпуска другого раздела.The reasons for doing so, and what moving actually looks like, will have to wait for another topic. Однако можно считать, что "x" в имени xvalue означает "expert-only" (только для специалистов), если вам так проще.But you can think of the "x" in "xvalue" as meaning "expert-only" if that helps. Приведя значение lvalue к xvalue (относящемуся к типу rvalue), мы делаем возможной его привязку к ссылке на rvalue.By casting an lvalue into an xvalue (a kind of rvalue), the value then becomes capable of being bound to an rvalue reference.

Ниже приведены другие примеры значений xvalue — вызов функции, возвращающей неименованную ссылку на rvalue, которая обращается к элементу xvalue.Here are two other examples of xvalues—calling a function that returns an unnamed rvalue reference, and accessing a member of an xvalue.

struct A { int m; };
A&& f();
f(); // This expression is an xvalue...
f().m; // ...and so is this.

prvalue (!i&m)prvalue (!i&m)

Значение prvalue ("чистое значение rvalue", относится к типу rvalue) не имеет идентификатора, но может быть перемещено.A prvalue (pure rvalue; a kind of rvalue) doesn't have identity, but is movable. Обычно это временные значения, результат вызова функции, которая возвращает значение, или результат вычисления другого выражения, не являющийся значением glvalue.These are typically temporaries, the result of calling a function that returns by value, or the result of evaluating any other expression that's not a glvalue,

rvalue (m)rvalue (m)

Значение rvalue допускает перемещение.An rvalue is movable. Ссылка на rvalue всегда указывает на rvalue (значение, содержимое которого не предполагается сохранять).An rvalue reference always refers to an rvalue (a value whose contents it's assumed we don't need to preserve).

Но является ли ссылка на rvalue самим значением rvalue?But, is an rvalue reference itself an rvalue? Неименованная ссылка на rvalue (см. примеры кода с xvalue выше) является значением xvalue, то есть она представляет собой значение rvalue.An unnamed rvalue reference (like the ones shown in the xvalue code examples above) is an xvalue so, yes, it's an rvalue. Ее предпочтительнее привязывать к параметру функции ссылки на rvalue, например, конструктора перемещения.It prefers to be bound to an rvalue reference function parameter, such as that of a move constructor. И наоборот (что может противоречить логике), если у ссылки на rvalue есть имя, то выражение, состоящее из этого имени, является значением lvalue.Conversely (and perhaps counter-intuitively), if an rvalue reference has a name, then the expression consisting of that name is an lvalue. Поэтому его невозможно привязать к параметру ссылки на rvalue.So it can't be bound to an rvalue reference parameter. Но это легко обеспечить — нужно просто привести его к неименованной ссылке на rvalue (xvalue).But it's easy to make it do so—just cast it to an unnamed rvalue reference (an xvalue) again.

void foo(A&) { ... }
void foo(A&&) { ... }
void bar(A&& a) // a is a named rvalue reference; it's an lvalue.
{
    foo(a); // Calls foo(A&).
    foo(static_cast<A&&>(a)); // Calls foo(A&&).
}
A&& get_by_rvalue_ref() { ... } // This unnamed rvalue reference is an xvalue.

!i&!m!i&!m

Мы еще не рассмотрели тип значения, у которого нет идентификатора и которое невозможно переместить.The kind of value that doesn't have identity and isn't movable is the one combination that we haven't yet discussed. Но его можно проигнорировать, так как эта категория ничем не полезна для языка C++.But we can disregard it, because that category isn't a useful idea in the C++ language.

Правила сворачивания ссылокReference-collapsing rules

Несколько ссылок & в выражении (ссылка на lvalue на ссылку на lvalue или ссылка на rvalue на ссылку rvalue) отменяют друг друга.Multiple like references in an expression (an lvalue reference to an lvalue reference, or an rvalue reference to an rvalue reference) cancel one another out.

  • A& & сворачивается в A&.A& & collapses into A&.
  • A&& && сворачивается в A&&.A&& && collapses into A&&.

Несколько ссылок && в выражении сворачиваются в ссылку на lvalue.Multiple unlike references in an expression collapse to an lvalue reference.

  • A& && сворачивается в A&.A& && collapses into A&.
  • A&& & сворачивается в A&.A&& & collapses into A&.

Пересылаемые ссылкиForwarding references

В заключительном разделе ссылки на rvalue, которые мы уже изучили, сравниваются с другим понятием — пересылаемыми ссылками.This final section contrasts rvalue references, which we've already discussed, with the different concept of a forwarding reference.

void foo(A&& a) { ... }
  • A&& является ссылкой на rvalue, как было показано ранее.A&& is an rvalue reference, as we've seen. Понятия "постоянный" и "переменный" не применяются к ссылкам на rvalue.Const and volatile don't apply to rvalue references.
  • foo принимает только значения rvalue типа A.foo accepts only rvalues of type A.
  • Смысл ссылок на rvalue (например, A&&) состоит в том, что можно создать перегрузку, которая оптимизирована на случай передачи временного значения (или другого значения rvalue).The reason rvalue references (such as A&&) exist is so that you can author an overload that's optimized for the case of a temporary (or other rvalue) being passed.
template <typename _Ty> void bar(_Ty&& ty) { ... }
  • _Ty&& — это пересылаемая ссылка._Ty&& is a forwarding reference. В зависимости от того, что передается в bar, типом _Ty может быть константа или не константа независимо от того, переменный он или нет.Depending what you pass to bar, type _Ty could be const/non-const independently of volatile/non-volatile.
  • bar принимает любое значение lvalue или rvalue типа _Ty.bar accepts any lvalue or rvalue of type _Ty.
  • При передаче значения lvalue пересылаемая ссылка принимает вид _Ty& &&, который сворачивается в ссылку на lvalue _Ty&.Passing an lvalue causes the forwarding reference to become _Ty& &&, which collapses to the lvalue reference _Ty&.
  • При передаче значения rvalue пересылаемая ссылка принимает вид _Ty&& &&, который сворачивается в ссылку на rvalue _Ty&&.Passing an rvalue causes the forwarding reference to become _Ty&& &&, which collapses to the rvalue reference _Ty&&.
  • Пересылаемые ссылки (например, _Ty&&) предназначены не для оптимизации, а для того, чтобы можно было просто и эффективно переслать передаваемое значение.The reason forwarding references (such as _Ty&&) exist is not for optimization, but to take what you pass to them and to forward it on transparently and efficiently. Вероятно, вы столкнетесь с пересылаемыми ссылками только в том случае, если будете создавать (или внимательно изучать) код библиотеки — например, стандартную функцию, которая пересылает аргументы конструктора.You're likely to encounter a forwarding reference only if you write (or closely study) library code—for example, a factory function that forwards on constructor arguments.

ИсточникиSources

  • [Stroustrup, 2013] Страуструп Б. "The C++ Programming Language, Fourth Edition".[Stroustrup, 2013] B. Stroustrup: The C++ Programming Language, Fourth Edition. Addison-Wesley.Addison-Wesley. 2013.2013.