тег документации
Тег позволяет описать методы доступа к свойству и свойствам. При добавлении свойства с помощью мастера кода в интегрированной среде разработки Visual Studio он добавит тег для нового свойства. Необходимо вручную добавить тег, чтобы описать значение, которое представляет свойство.
Синтаксис
/// property-description
Параметры
property-description
Описание свойства.
Замечания
Скомпилируйте их для /doc обработки примечаний документации к файлу.
Пример
// xml_value_tag.cpp // compile with: /LD /clr /doc // post-build command: xdcmake xml_value_tag.dll using namespace System; /// Text for class Employee. public ref class Employee < private: String ^ name; /// Name accesses the value of the name data member public: property String ^ Name < String ^ get() < return name; >void set(String ^ i) < name = i; >> >;
value (Справочник по C#)
Контекстное ключевое слово value используется в методе доступа set в объявлениях свойства и индексатора. Оно аналогично входному параметру метода. Ключевое слово value ссылается на значение, которое клиентский код пытается присвоить свойству или индексатору. В приведенном ниже примере класс MyDerivedClass имеет свойство с именем Name , в котором используется параметр value для присвоения новой строки резервному полю name . С точки зрения клиентского кода эта операция выглядит как простое присвоение.
class MyBaseClass < // virtual auto-implemented property. Overrides can only // provide specialized behavior if they implement get and set accessors. public virtual string Name < get; set; >// ordinary virtual property with backing field private int _num; public virtual int Number < get < return _num; >set < _num = value; >> > class MyDerivedClass : MyBaseClass < private string _name; // Override auto-implemented property with ordinary property // to provide specialized accessor behavior. public override string Name < get < return _name; >set < if (!string.IsNullOrEmpty(value)) < _name = value; >else < _name = "Unknown"; >> > >
Дополнительные сведения см. в статьях Свойства и Индексаторы.
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
- Справочник по C#
- Руководство по программированию на C#
- Ключевые слова в C#
Совместная работа с нами на GitHub
Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.
The .NET documentation is open source. Provide feedback here.
Обратная связь
Отправить и просмотреть отзыв по
Что такое value в c
В C++ все переменные являются l-values. l-value (в переводе «л-значение», произносится как «ел-валью») – это значение, которое имеет свой адрес в памяти. Поскольку все переменные имеют адреса, то они все являются l-values (например: переменные a, b, c – все являются l-values). l от слова «left», так как только значения l-values могут находиться в левой стороне в операциях присваивания (в противном случае мы получим ошибку). Например, выражение 9 = 10; вызовет ошибку компилятора, так как 9 не является l-value. Число 9 не имеет своего адреса в памяти и, таким образом, мы ничего не можем ему присвоить (9 равно 9, и ничего здесь не изменить).
Противоположностью l-value является r-value (в переводе «р-значение», произносится как «ер-валью»). r-value – это значение, которое не имеет постоянного адреса в памяти. Примерами могут быть единичные числа (например, 7, которое вычисляется в 7) или выражения (например, x + 3, которое вычисляется в значение «х плюс 3»).
Вот несколько примеров операций присваивания с использованием r-values:
int a; // объявляем целочисленную переменную a
a = 5; // 5 вычисляется в 5, которое затем присваивается переменной а
a = 4 + 6; // 4 + 6 вычисляется в 10, которое затем присваивается переменной а
int b; // объявляем целочисленную переменную b
b = a; // a вычисляется в 10 (с предыдущих операций), которое затем присваивается переменной b
b = b; // b вычисляется в 10, которое затем присваивается переменной b (ничего не происходит)
b = b + 2; // b + 2 вычисляется в 12, которое затем присваивается переменной b
Давайте детальнее рассмотрим последнюю операцию присваивания:
Здесь переменная b используется в двух различных контекстах. Слева b используется как l-value (переменная с адресом в памяти), справа b используется как r-value и производит отдельное значение (в данном случае 12). При выполнении этого стейтмента, компилятор видит следующее:
И здесь уже становится понятным, какое значение присваивается переменной b.
Всё, что нужно запомнить – это то, что в левой стороне операции присваивания всегда должно находиться l-value (которое имеет свой адрес в памяти), а в правой стороне операции присваивания – r-value (которое производит какое-то значение).
- Стандарт языка программирования Си++. Где его найти?
- Как сделать массив объектов, у которых конструктор имеет аргументы
- Борьба с ошибкой линковки «In function . undefined reference to» при сборке с заранее скомпиленными библиотеками
- Создание объектов в цикле
- Про dynamic_cast в сравнении с Pascal
- Проверка кода: какие статические анализаторы существуют?
- Про нужность виртуального деструктора
- Как срабатывают конструкторы, деструкторы и инициализация переменных в C++
- Многомерные динамические массивы
- Как работать с указателями на объект
- Синтаксис объявления указателей на функции в С++ — как построить тип указателя на функцию
- Указатели на функцию в языке Си/Си++
- Как получить адрес функции в языке C++
- Массивы указателей на функцию в языке Си/Си++
- Указатель в языке C++, хорошее объяснение
- Как сделать FastCGI сервер на C/C++
- Концепция объектно-ориентированной парадигмы предельно проста.
- Небольшой логгер стека вызовов для C++
- Руководство новичка по эксплуатации компоновщика в C/C++
- Как препроцессор узнает полный путь к заголовочному файлу библиотеки в GCC
- Лямбда-функции в C++ (стандарт С++11)
- Лямбда-выражения в C++0x (С++11)
- Синтаксис лямбда выражения с языке C++
- Что обозначает декларация const в описании метода
- C++: пример использования const-указателя
- C++: учебные материалы по контейнерным классам и алгоритмам
- Интервью с Бьерном Страуструпом о языке C++
- C++: Ключевое слово explicit. Явные конструкторы
- Модификаторы public, private и protected в C++
- Права доступа при наследовании в C++
- Конструктор копирования в C++: объяснение и пример использования
- Константные методы и константные указатели в C++, краткие примеры
- Книга «Введение в язык Си++», третье издание
- Дружественные функции (методы) в С++
- Дружественные классы в С++
- Еще одна попытка объяснить, что такое ссылка и указатель, и чем они отличаются
- Что такое explicitly shared объекты и implicitly shared и чем они отличаются (краткое объяснение)
- Приведение типов в C++. Терминология, динамический полиморфизм
- Приведение типов в C++
- Указатели, ссылки и массивы в C и C++: точки над i tutorial (подробное и простое объяснение)
- Отличие ссылок от указателей в языке C++
- Как в C++ различать «константный указатель» и «указатель на константу»
- Подходы к разработке встраиваемого ПО на C++: Шаблоны
- Пример перебора std::unordered_map на C++
- Пример передачи #define определений через флаги компилятора в GCC
- Что такое lvalue и rvalue в языке C++
- Что такое l-value и r-value в С++. Простое и короткое объяснение
- Почему строковый литерал в C++ является lvalue?
- Значения Lvalue и Rvalue
- Вопросы и ответы на RSDN. Что это такое lvalue и rvalue?
- Понимание преинкремента и постинкремента в языке C++
- Приоритет операций в языке C++
- Онлайн компиляторы C++
- Как в C++ можно держать основные настройки программы и прочие конфигурирующие данные не в глобальной области видимости
- Порядок инициализации в конструкторах
- Как научиться понимать синтаксис языка C++
- Возврат значений по ссылке, по адресу (указателю) и по значению в C++
- Понимание наследования и механизма виртуальных методов в C++
- Особенности модификатора Const в C++ при работе с указателями и ссылками
- Глава книги «C++. Практика многопоточного программирования» — Разработка конкурентного кода
- Как сделать константное свойство в классе в языке C++
- Примеры работы с байтами через union и через memcpy
- Особенности языка C и C-style кода
- Как скомпилировать C++ программу с минимальным размером бинарника в GCC
- Понимание синтаксиса объявления указателей и массивов
- Техника поиска кода, использующего конструктор копирования или оператор копирования в языке Си++
- Рабочий пример простого парсера математических выражений
- Еще один пример простого парсера математических выражений, с поддержкой именованных констант
- Как узнать, какие директории будут по-умолчанию включены в INCLUDE path при компиляции C/C++ программ в GCC
- Кратко: что делают модификаторы override и final
- Переменные в единицах трансляции: размещение объектов в памяти и время жизни объектов
- Создание шаблонной фабрики объектов в языке C++
- Как бороться с ошибкой компиляции undefined reference to `std::cout’
- Как получить тип переменной в языке C++ в виде строки
- Размеры базовых математических типов int, long в различных ОС в зависимости от битности
- Пример использования constexpr и operator для задания величин с размерностью
Категории выражений в C++
Категории выражений, такие как lvalue и rvalue, относятся, скорее, к фундаментальным теоретическим понятиям языка C++, чем к практическим аспектам его использования. По этой причине многие даже опытные программисты достаточно смутно представляют себе, что они означают. В этой статье я постараюсь максимально просто объяснить значение этих терминов, разбавляя теорию практическими примерами. Сразу оговорюсь: статья не претендует на максимально полное и строгое описание категорий выражений, за подробностями я рекомендую обращаться непосредственно в первоисточник: Стандарт языка C++.
В статье будет довольно много англоязычных терминов, связано это с тем, что некоторые из них сложно перевести на русский, а другие переводятся в разных источниках по-разному. Поэтому я буду часто указывать англоязычные термины, выделяя их курсивом.
Немного истории
Термины lvalue и rvalue появились ещё в языке C. Стоит отметить, что путаница была заложена в терминологию изначально, потому как относятся они к выражениям (expressions), а не к значениям (values). Исторически lvalue – это то, что может быть слева (left) от оператора присваивания, а rvalue – то, что может быть только справа (right).
lvalue = rvalue;
Однако, такое определение несколько упрощает и искажает суть. Стандарт C89 определял lvalue как object locator, т.е. объект с идентифицируемым местом в памяти. Соответственно, всё, что не подходило под это определение, входило в категорию rvalue.
Бьярн спешит на помощь
В языке C++ терминология категорий выражений достаточно сильно эволюционировала, в особенности после принятия Стандарта C++11, где вводились понятия rvalue-ссылок и семантики перемещения (move semantics). История появления новой терминологии интересно описана в статье Страуструпа “New” Value Terminology.
В основу новой более строгой терминологии легли 2 свойства:
- наличие идентичности (identity) – т. е. какого-то параметра, по которому можно понять, ссылаются ли два выражения на одну и ту же сущность или нет (например, адрес в памяти);
- возможность перемещения (can be moved from) – поддерживает семантику перемещения.
Обладающие идентичностью выражения обобщены под термином glvalue (generalized values), перемещаемые выражения называются rvalue. Комбинации двух этих свойств определили 3 основные категории выражений:
| Обладают идентичностью | Лишены идентичности | |
|---|---|---|
| Не могут быть перемещены | lvalue | – |
| Могут быть перемещены | xvalue | prvalue |
На самом деле, в Стандарте C++17 появилось понятие избегание копирования (copy elision) – формализация ситуаций, когда компилятор может и должен избегать копирования и перемещения объектов. В связи с этим, prvalue не обязательно могут быть перемещены. Подробно и с примерами об этом можно почитать вот тут. Впрочем, это не влияет на понимание общей схемы категорий выражений.
В современном Стандарте C++ структура категорий приводится в виде вот такой схемы:
Разберём в общих чертах свойства категорий, а также выражения языка, которые входят в каждую из категорий. Сразу отмечу, что приведённые ниже списки выражений для каждой категории не могут считаться полными, для более точной и подробной информации следует обратиться напрямую к Стандарту C++.
glvalue
Выражения категории glvalue обладают следующими свойствами:
- могут быть неявно преобразованы в prvalue;
- могут быть полиморфными, т. е. для них имеют смысл понятия статического и динамического типа;
- не могут иметь тип void – это напрямую следует из свойства наличия идентичности, ведь для выражений типа void нет такого параметра, который позволил бы отличать их одно от другого;
- могут иметь неполный тип (incomplete type), например, в виде forward declaration (если это разрешено для конкретного выражения).
rvalue
Выражения категории rvalue обладают следующими свойствами:
- нельзя получить адрес rvalue в памяти – это напрямую следует из свойства отсутствия идентичности;
- не могут находиться в левой части оператора присваивания или составного присваивания;
- могут использоваться для инициализации константной lvalue-ссылки или rvalue-ссылки, при этом время жизни объекта расширяется до времени жизни ссылки;
- если используются как аргумент при вызове функции, у которой есть 2 перегруженные версии: одна принимает константную lvalue-ссылку, а другая – rvalue-ссылку, то выбирается версия, принимающая rvalue-ссылку. Именно это свойство используется при реализации семантики перемещения (move semantics):
class A < public: A() = default; A(const A&) < std::cout A(A&&) < std::cout >; . A a; A b(a); // Вызывается A(const A&) A c(std::move(a)); // Вызывается A(A&&)
Технически, A&& является rvalue и может использоваться для инициализации как константной lvalue-ссылки, так и rvalue-ссылки. Но благодаря этому свойству никакой неоднозначности нет, выбирается вариант конструктора, принимающий rvalue-ссылку.
lvalue
- все свойства glvalue (см. выше);
- можно взять адрес (используя встроенный унарный оператор & );
- модифицируемые lvalue могут находиться в левой части оператора присваивания или составных операторов присваивания;
- могут использоваться для инициализации ссылки на lvalue (как константной, так и неконстантной).
К категории lvalue относятся следующие выражения:
- имя переменной, функции или поле класса любого типа. Даже если переменная является rvalue-ссылкой, имя этой переменной в выражении является lvalue;
void func() <> . auto* func_ptr = &func; // порядок: получаем указатель на функцию auto& func_ref = func; // порядок: получаем ссылку на функцию int&& rrn = int(123); auto* pn = &rrn; // порядок: получаем адрес объекта auto& rn = rrn; // порядок: инициализируем lvalue-ссылку
- вызов функции или перегруженного оператора, возвращающего lvalue-ссылку, либо выражение преобразования к типу lvalue-ссылки;
- встроенные операторы присваивания, составные операторы присваивания ( = , += , /= и т. д.), встроенные преинкремент и предекремент ( ++a , —b ), встроенный оператор разыменования указателя ( *p );
- встроенный оператор обращения по индексу ( a[n] или n[a] ), когда один из операндов – lvalue массив;
- вызов функции или перегруженного оператора, возвращающего rvalue-ссылку на функцию;
- строковый литерал, например «Hello, world!» .
Строковый литерал отличается от всех остальных литералов в языке C++ именно тем, что является lvalue (хотя и неизменяемым). Например, можно получить его адрес:
auto* p = &”Hello, world!”; // тут константный указатель, на самом деле
prvalue
- все свойства rvalue (см. выше);
- не могут быть полиморфными: статический и динамический типы выражения всегда совпадают;
- не могут быть неполного типа (кроме типа void, об этом будет сказано ниже);
- не могут иметь абстрактный тип или быть массивом элементов абстрактного типа.
К категории prvalue относятся следующие выражения:
- литерал (кроме строкового), например 42 , true или nullptr ;
- вызов функции или перегруженного оператора, который возвращает не ссылку ( str.substr(1, 2) , str1 + str2 , it++ ) или выражение преобразования к нессылочному типу (например static_cast(x) , std::string<> , (int)42 );
- встроенные постинкремент и постдекремент ( a++ , b— ), встроенные математические операции ( a + b , a % b , a & b , a = b , и т.д.), встроенная операция взятия адреса ( &a );
- указатель this;
- элемент перечисления;
- нетиповой параметр шаблона, если он – не класс;
- лямбда-выражение, например [](int x) < return x * x; >.
xvalue
Примеры выражений категории xvalue:
- вызов функции или встроенного оператора, возвращающего rvalue-ссылку, например std::move(x);
и в самом деле, для результата вызова std::move() нельзя получить адрес в памяти или инициализировать им ссылку, но в то же время, это выражение может быть полиморфным:
struct XA < virtual void f() < std::cout >; struct XB : public XA < virtual void f() < std::cout >; XA&& xa = XB(); auto* p = &std::move(xa); // ошибка auto& r = std::move(xa); // ошибка std::move(xa).f(); // выведет “XB::f()”
- встроенный оператор обращения по индексу ( a[n] или n[a] ), когда один из операндов – rvalue-массив.
Некоторые особые случаи
Оператор запятая
Для встроенного оператора запятая (comma operator) категория выражения всегда соответствует категории выражения второго операнда.
int n = 0; auto* pn = &(1, n); // lvalue auto& rn = (1, n); // lvalue 1, n = 2; // lvalue auto* pt = &(1, int(123)); // ошибка, rvalue auto& rt = (1, int(123)); // ошибка, rvalue
Выражения типа void
Вызовы функций, возвращающих void, выражения преобразования типов к void, а также выбрасывания исключений (throw) считаются выражениями категории prvalue, но их нельзя использовать для инициализации ссылок или в качестве аргументов функций.
Тернарный оператор сравнения
Определение категории выражения a ? b : c – случай нетривиальный, всё зависит от категорий второго и третьего аргументов ( b и c ):
- если b или c имеют тип void, то категория и тип всего выражения соответствуют категории и типу другого аргумента. Если оба аргумента имеют тип void, то результат – prvalue типа void;
- если b и c являются glvalue одного типа, то и результат является glvalue этого же типа;
- в остальных случаях результат prvalue.
Для тернарного оператора определён целый ряд правил, по которым к аргументам b и c могут применяться неявные преобразования, но это несколько выходит за темы статьи, интересующимся рекомендую обратиться к разделу Стандарта Conditional operator [expr.cond].
int n = 1; int v = (1 > 2) ? throw 1 : n; // lvalue, т.к. throw имеет тип void, соответственно берём категорию n ((1 < 2) ? n : v) = 2; // тоже lvalue, выглядит странно, но работает ((1 < 2) ? n : int(123)) = 2; // так не получится, т.к. теперь всё выражение prvalue
Обращения к полям и методам классов и структур
Для выражений вида a.m и p->m (тут речь о встроенном операторе -> ) действуют следующие правила:
- если m – элемент перечисления или нестатический метод класса, то всё выражение считается prvalue (хотя ссылку таким выражением инициализировать не получится);
- если a – это rvalue, а m – нестатическое поле нессылочного типа, то всё выражение относится к категории xvalue;
- в остальных случаях это lvalue.
Для указателей на члены класса ( a.*mp и p->*mp ) правила похожие:
- если mp – это указатель на метод класса, то всё выражение считается prvalue;
- если a – это rvalue, а mp – указатель на поле данных, то всё выражение относится к xvalue;
- в остальных случаях это lvalue.
Битовые поля
Битовые поля – удобный инструмент для низкоуровнего программирования, однако, их реализация несколько выпадает из общей структуры категорий выражений. Например, обращение к битовому полю вроде бы является lvalue, т. к. может присутствовать в левой части оператора присваивания. В то же время, взять адрес битового поля или инициализировать им неконстантную ссылку не получится. Константную ссылку на битовое поле инициализировать можно, но при этом будет создана временная копия объекта:
struct BF < int f:3; >; BF b; b.f = 1; // OK auto* pb = &b.f; // ошибка auto& rb = b.f; // ошибка
Вместо заключения
Как я и упоминал во вступлении, приведённое описание не претендует на полноту, а лишь даёт общее представление о категориях выражений. Это представление позволит немного лучше понимать параграфы Стандарта и сообщения об ошибках компилятора.
