名稱空間
變體
操作

值類別

來自 cppreference.com
< cpp‎ | 語言
 
 
C++ 語言
表示式
值類別
求值順序
替代表示
字面量
布林字面量 - 整數字面量 - 浮點字面量
字元字面量 - 字串字面量 - nullptr (C++11)
使用者定義 (C++11)
工具
屬性 (C++11)
型別
typedef 宣告
類型別名宣告 (C++11)
型別轉換
記憶體分配
類特有的函式屬性
explicit (C++11)
static

特殊成員函式
模板
雜項
 
 

每個 C++ 表示式(運算子及其運算元、字面量、變數名等)都由兩個獨立的屬性來描述:一個型別和一個值類別。每個表示式都有一個非引用型別,並且每個表示式都精確地屬於三種主要值類別之一:prvaluexvaluelvalue

  • 一個 glvalue(“廣義”左值)是一個表示式,其求值確定了一個物件或函式的身份;
  • 一個 prvalue(“純”右值)是一個表示式,其求值
  • 計算內建運算子運算元的值(這種 prvalue 沒有結果物件),或者
  • 初始化一個物件(這種 prvalue 被認為有一個結果物件)。
結果物件可以是一個變數,一個由 new-expression 建立的物件,一個由 臨時實質化 建立的臨時物件,或者它們的成員。請注意,非 void 被丟棄的 表示式具有結果物件(實質化的臨時物件)。此外,每個類和陣列 prvalue 都有一個結果物件,除非它是 decltype 的運算元;
  • 一個 xvalue(“將過期”的值)是一個 glvalue,它表示一個其資源可以被重用的物件;
  • 一個 lvalue 是一個不是 xvalue 的 glvalue;
擴充套件內容

歷史地,被稱為左值,因為左值可以出現在賦值表示式的左側。通常情況並非總是如此

void foo();
 
void baz()
{
    int a; // Expression `a` is lvalue
    a = 4; // OK, could appear on the left-hand side of an assignment expression
 
    int &b{a}; // Expression `b` is lvalue
    b = 5; // OK, could appear on the left-hand side of an assignment expression
 
    const int &c{a}; // Expression `c` is lvalue
    c = 6;           // ill-formed, assignment of read-only reference
 
    // Expression `foo` is lvalue
    // address may be taken by built-in address-of operator
    void (*p)() = &foo;
 
    foo = baz; // ill-formed, assignment of function
}
  • 一個 rvalue 是一個 prvalue 或一個 xvalue;
擴充套件內容

歷史地,被稱為右值,因為右值可以出現在賦值表示式的右側。通常情況並非總是如此

#include <iostream>
 
struct S
{
    S() : m{42} {}
    S(int a) : m{a} {}
    int m;
};
 
int main()
{
    S s;
 
    // Expression `S{}` is prvalue
    // May appear on the right-hand side of an assignment expression
    s = S{};
 
    std::cout << s.m << '\n';
 
    // Expression `S{}` is prvalue
    // Can be used on the left-hand side too
    std::cout << (S{} = S{7}).m << '\n';
}

輸出

42
7

注意:此分類法在過去的 C++ 標準修訂版中經歷了重大變化,詳情請參閱下面的歷史

擴充套件內容

儘管有它們的名稱,這些術語是用來分類表示式,而不是值。

#include <type_traits>
#include <utility>
 
template <class T> struct is_prvalue : std::true_type {};
template <class T> struct is_prvalue<T&> : std::false_type {};
template <class T> struct is_prvalue<T&&> : std::false_type {};
 
template <class T> struct is_lvalue : std::false_type {};
template <class T> struct is_lvalue<T&> : std::true_type {};
template <class T> struct is_lvalue<T&&> : std::false_type {};
 
template <class T> struct is_xvalue : std::false_type {};
template <class T> struct is_xvalue<T&> : std::false_type {};
template <class T> struct is_xvalue<T&&> : std::true_type {};
 
int main()
{
    int a{42};
    int& b{a};
    int&& r{std::move(a)};
 
    // Expression `42` is prvalue
    static_assert(is_prvalue<decltype((42))>::value);
 
    // Expression `a` is lvalue
    static_assert(is_lvalue<decltype((a))>::value);
 
    // Expression `b` is lvalue
    static_assert(is_lvalue<decltype((b))>::value);
 
    // Expression `std::move(a)` is xvalue
    static_assert(is_xvalue<decltype((std::move(a)))>::value);
 
    // Type of variable `r` is rvalue reference
    static_assert(std::is_rvalue_reference<decltype(r)>::value);
 
    // Type of variable `b` is lvalue reference
    static_assert(std::is_lvalue_reference<decltype(b)>::value);
 
    // Expression `r` is lvalue
    static_assert(is_lvalue<decltype((r))>::value);
}

目錄

[編輯] 主要類別

[編輯] lvalue

以下表達式是lvalue 表示式

擴充套件內容
void foo() {}
 
void baz()
{
    // `foo` is lvalue
    // address may be taken by built-in address-of operator
    void (*p)() = &foo;
}
struct foo {};
 
template <foo a>
void baz()
{
    const foo* obj = &a;  // `a` is an lvalue, template parameter object
}
  • 函式呼叫或過載運算子表示式,其返回型別為左值引用,例如 std::getline(std::cin, str)std::cout << 1str1 = str2++it
擴充套件內容
int& a_ref()
{
    static int a{3};
    return a;
}
 
void foo()
{
    a_ref() = 5;  // `a_ref()` is lvalue, function call whose return type is lvalue reference
}
  • a = ba += ba %= b 以及所有其他內建賦值和複合賦值表示式;
  • ++a--a,內建的前置增量和前置減量表示式;
  • *p,內建的解引用表示式;
  • a[n]p[n],內建的下標表示式,其中 a[n] 中的一個運算元是陣列左值(C++11 起)
  • a.m物件的成員表示式,除了 m 是成員列舉器或非靜態成員函式,或者 a 是右值且 m 是物件型別的非靜態資料成員;
擴充套件內容
struct foo
{
    enum bar
    {
        m // member enumerator
    };
};
 
void baz()
{
    foo a;
    a.m = 42; // ill-formed, lvalue required as left operand of assignment
}
struct foo
{
    void m() {} // non-static member function
};
 
void baz()
{
    foo a;
 
    // `a.m` is a prvalue, hence the address cannot be taken by built-in
    // address-of operator
    void (foo::*p1)() = &a.m; // ill-formed
 
    void (foo::*p2)() = &foo::m; // OK: pointer to member function
}
struct foo
{
    static void m() {} // static member function
};
 
void baz()
{
    foo a;
    void (*p1)() = &a.m;     // `a.m` is an lvalue
    void (*p2)() = &foo::m;  // the same
}
  • p->m,內建的指標成員表示式,除了 m 是成員列舉器或非靜態成員函式;
  • a.*mp指向物件成員的指標表示式,其中 a 是左值且 mp 是指向資料成員的指標;
  • p->*mp,內建的指向指標成員的指標表示式,其中 mp 是指向資料成員的指標;
  • a, b,內建的逗號表示式,其中 b 是左值;
  • a ? b : c,某些 bc三元條件表示式(例如,當兩者都是相同型別的左值時,但請參閱定義瞭解詳情);
  • 字串字面量,例如 "Hello, world!"
  • 轉換為左值引用型別的轉換表示式,例如 static_cast<int&>(x)static_cast<void(&)(int)>(x)
  • 左值引用型別的非型別模板引數
template <int& v>
void set()
{
    v = 5; // template parameter is lvalue
}
 
int a{3}; // static variable, fixed address is known at compile-time
 
void foo()
{
    set<a>();
}
  • 函式呼叫或過載運算子表示式,其返回型別為函式右值引用;
  • 轉換為函式右值引用型別的轉換表示式,例如 static_cast<void(&&)(int)>(x)
(C++11 起)

屬性

  • glvalue(下述)。
  • 可以透過內建的取地址運算子獲取左值的地址:&++i[1]&std::endl 是有效表示式。
  • 可修改的左值可以用作內建賦值和複合賦值運算子的左運算元。
  • 左值可用於初始化左值引用;這為表示式標識的物件關聯一個新名稱。

[編輯] prvalue

以下表達式是 prvalue 表示式

  • 字面量字串字面量除外),例如 42truenullptr
  • 函式呼叫或過載運算子表示式,其返回型別為非引用,例如 str.substr(1, 2)str1 + str2it++
  • a++a--,內建的後置增量和後置減量表示式;
  • a + ba % ba & ba << b 以及所有其他內建算術表示式;
  • a && ba || b!a,內建的邏輯表示式;
  • a < ba == ba >= b 以及所有其他內建比較表示式;
  • &a,內建的取地址表示式;
  • a.m物件的成員表示式,其中 m 是成員列舉器或非靜態成員函式[2]
  • p->m,內建的指標成員表示式,其中 m 是成員列舉器或非靜態成員函式[2]
  • a.*mp指向物件成員的指標表示式,其中 mp 是指向成員函式的指標[2]
  • p->*mp,內建的指向指標成員的指標表示式,其中 mp 是指向成員函式的指標[2]
  • a, b,內建的逗號表示式,其中 b 是 prvalue;
  • a ? b : c,某些 bc三元條件表示式(請參閱定義瞭解詳情);
  • 轉換為非引用型別的轉換表示式,例如 static_cast<double>(x)std::string{}(int)42
  • this 指標;
  • 列舉器
  • 標量型別的非型別模板引數
template <int v>
void foo()
{
    // not an lvalue, `v` is a template parameter of scalar type int
    const int* a = &v; // ill-formed
 
    v = 3; // ill-formed: lvalue required as left operand of assignment
}
(C++11 起)
(C++20 起)

屬性

[編輯] xvalue

以下表達式是 xvalue 表示式

  • a.m物件的成員表示式,其中 a 是右值,m 是物件型別的非靜態資料成員;
  • a.*mp指向物件成員的指標表示式,其中 a 是右值,mp 是指向資料成員的指標;
  • a, b,內建的逗號表示式,其中 b 是 xvalue;
  • a ? b : c,某些 bc三元條件表示式(請參閱定義瞭解詳情);
  • 函式呼叫或過載運算子表示式,其返回型別為物件右值引用,例如 std::move(x)
  • a[n],內建的下標表示式,其中一個運算元是陣列右值;
  • 轉換為物件右值引用型別的轉換表示式,例如 static_cast<char&&>(x)
(C++11 起)
(C++17 起)
(C++23 起)

屬性

  • 同 rvalue(下述)。
  • 同 glvalue(下述)。

特別地,像所有右值一樣,xvalue 繫結到右值引用,並且像所有 glvalue 一樣,xvalue 可以是多型的,非類 xvalue 可以是cv 限定的。

擴充套件內容
#include <type_traits>
 
template <class T> struct is_prvalue : std::true_type {};
template <class T> struct is_prvalue<T&> : std::false_type {};
template <class T> struct is_prvalue<T&&> : std::false_type {};
 
template <class T> struct is_lvalue : std::false_type {};
template <class T> struct is_lvalue<T&> : std::true_type {};
template <class T> struct is_lvalue<T&&> : std::false_type {};
 
template <class T> struct is_xvalue : std::false_type {};
template <class T> struct is_xvalue<T&> : std::false_type {};
template <class T> struct is_xvalue<T&&> : std::true_type {};
 
// Example from C++23 standard: 7.2.1 Value category [basic.lval]
struct A
{
    int m;
};
 
A&& operator+(A, A);
A&& f();
 
int main()
{
    A a;
    A&& ar = static_cast<A&&>(a);
 
    // Function call with return type rvalue reference is xvalue
    static_assert(is_xvalue<decltype( (f()) )>::value);
 
    // Member of object expression, object is xvalue, `m` is a non-static data member
    static_assert(is_xvalue<decltype( (f().m) )>::value);
 
    // A cast expression to rvalue reference
    static_assert(is_xvalue<decltype( (static_cast<A&&>(a)) )>::value);
 
    // Operator expression, whose return type is rvalue reference to object
    static_assert(is_xvalue<decltype( (a + a) )>::value);
 
    // Expression `ar` is lvalue, `&ar` is valid
    static_assert(is_lvalue<decltype( (ar) )>::value);
    [[maybe_unused]] A* ap = &ar;
}

[編輯] 混合類別

[編輯] glvalue

一個 glvalue 表示式 既可以是 lvalue 也可以是 xvalue。

屬性

  • glvalue 可以透過左值到右值、陣列到指標或函式到指標的隱式轉換隱式轉換為 prvalue。
  • glvalue 可以是多型的:它所標識的物件的動態型別不一定是表示式的靜態型別。
  • glvalue 可以具有不完整型別,只要表示式允許。

[編輯] rvalue

一個 rvalue 表示式 既可以是 prvalue 也可以是 xvalue。

屬性

  • 右值的地址不能透過內建的取地址運算子獲取:&int()&i++[3]&42&std::move(x) 是無效的。
  • 右值不能用作內建賦值或複合賦值運算子的左運算元。
  • 右值可用於初始化 const 左值引用,在這種情況下,右值標識的臨時物件的生命週期將延長至引用作用域結束。
  • 右值可用於初始化右值引用,在這種情況下,右值標識的臨時物件的生命週期將延長至引用作用域結束。
  • 當用作函式引數時,並且函式有兩個過載可用,一個接受右值引用引數,另一個接受 const 左值引用引數時,右值會繫結到右值引用過載(因此,如果複製建構函式和移動建構函式都可用,右值引數會呼叫移動建構函式,賦值運算子也是如此)。
(C++11 起)

[編輯] 特殊類別

[編輯] 待決成員函式呼叫

表示式 a.mfp->mf,其中 mf非靜態成員函式,以及表示式 a.*pmfp->*pmf,其中 pmf指向成員函式的指標,都被歸類為 prvalue 表示式,但它們不能用於初始化引用、作為函式引數,或用於任何目的,除非作為函式呼叫運算子的左運算元,例如 (p->*pmf)(args)

[編輯] Void 表示式

返回 void 的函式呼叫表示式,轉換為 void 的轉換表示式,以及 throw-expression 都被歸類為 prvalue 表示式,但它們不能用於初始化引用或作為函式引數。它們可以用於被丟棄值的上下文(例如,單獨一行,作為逗號運算子的左運算元等)以及返回 void 的函式中的 return 語句。此外,throw-expression 可以用作條件運算子 ?: 的第二個和第三個運算元。

void 表示式沒有結果物件

(C++17 起)

[編輯] 位域

指定位域的表示式(例如 a.m,其中 a 是型別 struct A { int m: 3; } 的左值)是一個 glvalue 表示式:它可以作為賦值運算子的左運算元,但不能獲取其地址,並且不能將非 const 左值引用繫結到它。const 左值引用或右值引用可以從位域 glvalue 初始化,但會建立一個位域的臨時副本:它不會直接繫結到位域。

可移動表示式

雖然由任何變數名稱組成的表示式是左值表示式,但如果它出現在以下運算元中,則該表示式可能是可移動的

如果一個表示式是可移動的,那麼為了過載決議的目的,它既被視為右值又被視為左值(C++23 前)被視為右值(C++23 起)(因此它可能選擇移動建構函式)。詳見區域性變數和引數的自動移動

(C++11 起)

[編輯] 歷史

[編輯] CPL

程式語言 CPL 首先引入了表示式的值類別:所有 CPL 表示式都可以以“右值模式”求值,但只有某些型別的表示式在“左值模式”下有意義。當以右值模式求值時,表示式被視為計算值的規則(右側值,或 rvalue)。當以左值模式求值時,表示式實際上給出一個地址(左側值,或 lvalue)。這裡的“左”和“右”代表“賦值的左側”和“賦值的右側”。

[編輯] C

C 程式語言採用了類似的分類法,只是賦值的作用不再重要:C 表示式分為“左值表示式”和其他(函式和非物件值),其中“左值”表示標識物件的表示式,“定位器值”[4]

[編輯] C++98

2011 年之前的 C++ 遵循 C 模型,但將“rvalue”這個名稱恢復給非左值表示式,將函式變為左值,並添加了規則:引用可以繫結到左值,但只有 const 引用可以繫結到右值。一些非左值 C 表示式在 C++ 中成為左值表示式。

[編輯] C++11

隨著 C++11 中移動語義的引入,值類別被重新定義以描述表示式的兩個獨立屬性[5]

  • 具有身份:可以透過比較物件或函式(直接或間接獲得)的地址來確定表示式是否引用與另一個表示式相同的實體;
  • 可以從中移動移動建構函式移動賦值運算子或其他實現移動語義的函式過載可以繫結到該表示式。

在 C++11 中,那些

  • 有身份且不能移動的表示式稱為左值(lvalue)表示式;
  • 有身份且可以移動的表示式稱為將亡值(xvalue)表示式;
  • 沒有身份但可以移動的表示式稱為純右值(prvalue)表示式;
  • 沒有身份且不能移動的表示式不被使用[6]

具有身份的表示式稱為“glvalue 表示式”(glvalue 代表“廣義左值”)。左值和將亡值都是 glvalue 表示式。

可以從中移動的表示式稱為“rvalue 表示式”。純右值和將亡值都是 rvalue 表示式。

[編輯] C++17

在 C++17 中,在某些情況下複製消除被強制執行,這要求將 prvalue 表示式與其初始化的臨時物件分離,從而形成了我們今天的系統。請注意,與 C++11 的方案相反,prvalue 不再從中移動。

[編輯] 腳註

  1. 假設 i 具有內建型別或前置增量運算子過載為返回左值引用。
  2. 2.0 2.1 2.2 2.3 特殊右值類別,參見待決成員函式呼叫
  3. 假設 i 具有內建型別或者後置增量運算子未過載為返回左值引用。
  4. “C 社群內部對於左值(lvalue)的含義存在分歧,一方認為左值是任何形式的物件定位符,另一方則認為左值在賦值運算子的左側有意義。C89 委員會採納了將左值定義為物件定位符的定義。”—— ANSI C 原理,6.3.2.1/10。
  5. Bjarne Stroustrup 的 “新”值術語,2010 年。
  6. const prvalues(僅允許用於類型別)和 const xvalues 不繫結到 T&& 過載,但它們繫結到 const T&& 過載,這些過載也被標準歸類為“移動建構函式”和“移動賦值運算子”,滿足此分類目的的“可移動”定義。然而,此類過載不能修改其引數,在實踐中不使用;在沒有它們的情況下,const prvalues 和 const xvalues 繫結到 const T& 過載。

[編輯] 參考

  • C++23 標準 (ISO/IEC 14882:2024)
  • 7.2.1 值類別 [basic.lval]
  • C++20 標準 (ISO/IEC 14882:2020)
  • 7.2.1 值類別 [basic.lval]
  • C++17 標準 (ISO/IEC 14882:2017)
  • 6.10 左值和右值 [basic.lval]
  • C++14 標準 (ISO/IEC 14882:2014)
  • 3.10 左值和右值 [basic.lval]
  • C++11 標準 (ISO/IEC 14882:2011)
  • 3.10 左值和右值 [basic.lval]
  • C++98 標準 (ISO/IEC 14882:1998)
  • 3.10 左值和右值 [basic.lval]

[編輯] 缺陷報告

下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。

缺陷報告 應用於 釋出時的行為 正確的行為
CWG 616 C++11 透過成員訪問和成員訪問
右值指向成員的結果是純右值
重新分類為將亡值
CWG 1059 C++11 陣列純右值不能被 cv 限定 允許
CWG 1213 C++11 對陣列右值進行下標操作導致左值 重新分類為將亡值

[編輯] 另請參見

C 文件 關於 值類別

[編輯] 外部連結

1.  C++ 值類別和 decltype 揭秘 — David Mazières, 2021
2.  經驗性地確定表示式的值類別 — StackOverflow