cppdoc
Expressions
Each C++ expression is characterized by two independent properties: A type and a value category.
each expression belongs to exactly one of the three primary value categories: prvalue, xvalue, and lvalue.
Value categories
glvalue (“generalized” lvalue)
is an expression whose evaluation determines the identity of an object or function;
Properties:
- A glvalue may be implicitly converted to a prvalue with lvalue-to-rvalue, array-to-pointer, or function-to-pointer implicit conversion.
- A glvalue may be polymorphic: the dynamic type of the object it identifies is not necessarily the static type of the expression.
- A glvalue can have incomplete type, where permitted by the expression.
*lvalue
(so-called, historically, because lvalues could appear on the left-hand side of an assignment expression) is a glvalue that is not an xvalue;(lvalue's resources cannot be reused?)
examples
the name of a variable, a function, a template parameter object (since C++20), or a data member, regardless of type
a function call or an overloaded operator expression, whose return type is lvalue reference
a += b, and all other built-in assignment and compound assignment expressions;
++a and --a, the built-in pre-increment and pre-decrement expressions;
a, b
, the built-in comma expression, where b is an lvalue;a ? b : c
, the ternary conditional expression for certain b and c (e.g., when both are lvalues of the same type, but see definition for detail);*p, the built-in indirection expression
a string literal, such as
"Hello, world!"
;成员
a.m
, except wherem
is a member enumerator or a non-static member function, or where a is an rvalue andm
is a non-static data member of object type;p->m
, the built-in member of pointer expression, except wherem
is a member enumerator or a non-static member function;a.*mp
, the pointer to member of object expression, where a is an lvalue andmp
is a pointer to data member;
a cast expression to lvalue reference type, such as static_cast<int&>(x);
(since C++11) a function call or an overloaded operator expression, whose return type is rvalue reference to function; a cast expression to rvalue reference to function type, such as static_cast<void (&&)(int)>(x). ? a non-type template parameter of an lvalue reference type;
Properties:
Address of an lvalue may be taken by built-in address-of operator
&
A modifiable lvalue may be used as the left-hand operand of the built-in assignment and compound assignment operators.
An lvalue may be used to initialize an lvalue reference; this associates a new name with the object identified by the expression.
*xvalue (an “eXpiring” value)
is a glvalue that denotes an object whose resources can be reused;
examples
a.m
, the member of object expression, where a is an rvalue andm
is a non-static data member of an object type;a.*mp
, the pointer to member of object expression, where a is an rvalue andmp
is a pointer to data member;a ? b : c, the ternary conditional expression for certain b and c (see definition for detail);
(since C++11) a function call or an overloaded operator expression, whose return type is rvalue reference to object, such as std::move(x); a[n], the built-in subscript expression, where one operand is an array rvalue; a cast expression to rvalue reference to object type, such as static_cast<char&&>(x); (since C++17) any expression that designates a temporary object, after temporary materialization.
*prvalue (“pure” rvalue)
is an expression whose evaluation
- computes the value of an operand of a built-in operator (such prvalue has no result object), or
- initializes an object (such prvalue is said to have a result object).
examples
a literal (except for string literal), such as 42, true or nullptr;
a function call or an overloaded operator expression, whose return type is non-reference, such as str.substr(1, 2), str1 + str2, or it++;
a++ and a--, the built-in post-increment and post-decrement expressions;
a + b, and all other built-in arithmetic expressions;
&a, the built-in address-of expression;
成员
- a.m, the member of object expression, where
m
is a member enumerator or a non-static member function[2]; - p->m, the built-in member of pointer expression, where
m
is a member enumerator or a non-static member function[2]; - a.*mp, the pointer to member of object expression, where
mp
is a pointer to member function[2]; - p->*mp, the built-in pointer to member of pointer expression, where
mp
is a pointer to member function[2];
- a.m, the member of object expression, where
a, b
, the built-in comma expression, where b is an rvalue;a ? b : c
, the ternary conditional expression for certain b and c (see definition for detail);a cast expression to non-reference type, such as static_cast<double>(x), std::string, or (int)42;
the
this
pointer;an enumerator;
? a non-type template parameter of a scalar type;
(since C++11) a lambda expression, such as [](int x){ return x * x; };
(since C++20) a requires-expression, such as requires (T i) { typename T::type; }; a specialization of a concept, such as std::equality_comparable .
Properties:
A prvalue cannot be polymorphic: the dynamic type of the object it denotes is always the type of the expression.
A non-class non-array prvalue cannot be cv-qualified, unless it is materialized in order to be bound to a reference to a cv-qualified type (since C++17). (Note: a function call or cast expression may result in a prvalue of non-class cv-qualified type, but the cv-qualifier is generally immediately stripped out.)
A prvalue cannot have incomplete type (except for type void, see below, or when used in
decltype
specifier)A prvalue cannot have abstract class type or an array thereof.
rvalue
(so-called, historically, because rvalues could appear on the right-hand side of an assignment expression) is a prvalue or an xvalue.
Properties:
Address of an rvalue cannot be taken by built-in address-of operator: &int(), &i++[3], &42, and &std::move(x) are invalid.
An rvalue can't be used as the left-hand operand of the built-in assignment or compound assignment operators.
An rvalue may be used to initialize a const lvalue reference, in which case the lifetime of the object identified by the rvalue is extended until the scope of the reference ends.
(since C++11) An rvalue may be used to initialize an rvalue reference, in which case the lifetime of the object identified by the rvalue is extended until the scope of the reference ends. When used as a function argument and when two overloads of the function are available, one taking rvalue reference parameter and the other taking lvalue reference to const parameter, an rvalue binds to the rvalue reference overload (thus, if both copy and move constructors are available, an rvalue argument invokes the move constructor, and likewise with copy and move assignment operators).
C++11, expressions that:
- have identity and cannot be moved from are called lvalue expressions;
- have identity and can be moved from are called xvalue expressions;
- do not have identity and can be moved from are called prvalue ("pure rvalue") expressions;
- do not have identity and cannot be moved from are not used[6].
The expressions that have identity are called "glvalue expressions" (glvalue stands for "generalized lvalue"). Both lvalues and xvalues are glvalue expressions.
The expressions that can be moved from are called "rvalue expressions". Both prvalues and xvalues are rvalue expressions.
RTTI
- dynamic_cast
- typeid
|
tips
""
运算符
类型 type
- 对象类型是除了函数类型、引用类型以及可有 cv 限定的 void 类型以外的(可有 cv 限定的)类型(参阅 std::is_object);
- 标量类型是(可有 cv 限定的)算术、指针、成员指针、枚举和 std::nullptr_t (C++11 起) 类型(参阅 std::is_scalar);
- 平凡类型(参阅 std::is_trivial)、POD 类型(参阅 std::is_pod)、字面类型(参阅 std::is_literal_type)和其他类别,列于类型特征库中,或作为具名类型要求。
静态类型 动态类型
对程序进行编译时分析所得到的表达式的类型被称为表达式的静态类型。程序执行时静态类型不会更改。
如果某个泛左值表达式指代某个多态对象,那么它的最终派生对象的类型被称为它的动态类型。
定义
定义是完全定义了声明中所引入的实体的声明。除了以下情况外的声明都是定义:
- 无函数体的函数声明:
- 带有存储类说明符 extern 或者语言链接说明符(例如 extern "C")而无初始化器的所有声明:
- 在类定义中的非 inline (C++17 起) 静态数据成员的声明:
- 通过前置声明或通过在其他声明中使用详细类型说明符)对类名字进行的声明:
- 枚举的不可见声明:
- 模板形参的声明:
- 并非定义的函数声明中的形参声明:
- typedef 声明:
- 别名声明:
- using 声明:
ODR one definition rule
任何变量、函数、类类型、枚举类型、概念 (C++20 起)或模板,在每个翻译单元中都只允许有一个定义(其中部分可以有多个声明,但只允许有一个定义)。
非正式地说:
一个对象在它的值被读取(除非它是编译时常量)或写入,或它的地址被取,或者被引用绑定时,这个对象被 ODR 使用。
使用“所引用的对象在编译期未知”的引用时,这个引用被 ODR 使用。
1个函数在被调用或它的地址被取时,被 ODR 使用。 如果一个对象、引用或函数被 ODR 使用,那么程序中必须有它的定义;否则通常会有链接时错误。
常量表达式
定义能在编译时求值的表达式。
这种表达式能用做非类型模板实参、数组大小,并用于其他要求常量表达式的语境
常量表达式(constant expression)是
- 指代下列之一的左值 (C++14 前)泛左值 (C++14 起)核心常量表达式
- 拥有静态存储期且非临时的对象,或
- 拥有静态存储期的临时对象,但它的值满足下文对纯右值的约束(C++14 起),或
- 非立即 (C++20 起)函数
- 它的值满足下列约束的纯右值核心常量表达式
对象
C++ 程序可以创建、销毁、引用、访问并操作对象。
在 C++ 中,一个对象拥有这些性质:
- 大小(可以使用
sizeof
获取); - 对齐要求(可以使用
alignof
获取); - 存储期(自动、静态、动态、线程局部);
- 生存期(与存储期绑定或者临时)
- 类型;
- 值(可能是不确定的,例如默认初始化的非类类型);
- 名字(可选)。
生存期 lifetime
对象的生存期在以下时刻开始:
获得拥有它的类型的正确大小与对齐的存储,并且
完成它的初始化(如果存在)(包括不经由构造函数或经由平凡默认构造函数的默认初始化),除非
如果该对象是联合体成员或它的子对象,那么它的生存期在该联合体成员是联合体中的被初始化成员,或它被设为活跃才会开始,或者
如果该对象内嵌于联合体对象,那么它的生存期在平凡特殊成员函数赋值或构造含有它的联合体对象时开始,或者
数组对象的生存期可以因为该对象被 std::allocator::allocate 分配而开始。
对象的生存期在以下时刻结束:
- 如果该对象是非类类型,那么在销毁该对象时(可能经由伪析构函数调用销毁),或者
- 如果该对象是类类型,那么在析构函数调用开始时,或者
- 该对象所占据的存储被释放,或被不内嵌于它的对象所重用时。
对象的生存期与它的存储的生存期相同,或者内嵌于其中,参见存储期。
引用的生存期,从它的初始化完成之时开始,并与标量对象以相同的方式结束。
注意:被引用对象的生存期可能在引用的生存期结束之前就会结束,这会造成悬垂引用。
非静态数据成员和基类子对象的生存期按照类初始化顺序开始和结束。
临时对象的生存期
在下列情况中进行纯右值的实质化,从而能将它作为泛左值使用,即 (C++17 起)创建临时对象:
绑定引用到纯右值 | |
---|---|
以花括号初始化器列表初始化 std::initializer_list |
(C++11 起) |
函数返回纯右值 创建纯右值的类型转换(包括 T(a,b,c) 和 T{}), lambda 表达式(C++11 起) 要求对初始化器进行类型转换的复制初始化, 将引用绑定到不同但可以转换的类型,或绑定到位域。 |
(C++17 前) |
当对类类型的纯右值进行成员访问时 当对数组纯右值进行数组向指针转换或者下标运算时 对 sizeof 和 typeid 的不求值操作数当纯右值被用作弃值表达式时 如果实现支持的话,在函数调用表达式中传递或者返回可平凡复制 (TriviallyCopyable)的类型的对象(这对应的是在 CPU 寄存器中传递结构体的情况)时 |
(C++17 起) |
临时对象的实质化通常会尽可能地被推迟,以免创建不必要的临时对象:参见复制消除 | (C++17 起) |
所有临时对象的销毁都是在(词法上)包含创建它的位置的完整表达式的求值过程的最后一步进行的,而当创建了多个临时对象时,它们是以被创建的相反顺序销毁的。即便求值过程以抛出异常而终止也是如此。
对此有两种例外情况:
- 可以通过绑定到 const 左值引用或右值引用 (C++11 起)来延长临时对象的生存期,细节见引用初始化。
- 在对数组的某个元素使用含有默认实参的默认或复制构造函数进行初始化时,对该默认实参求值所创建或复制的临时对象的生存期将在该数组的下一个元素的初始化开始之前终止。
存储期 storage_duration
存储类说明符是一个名字的声明语法的声明说明符序列的一部分。它与名字的作用域一同控制名字的两个独立性质:它的“存储期”和它的“链接”。
auto
或 (C++11 前)无说明符 - 自动存储期。register
- 自动存储期,另提示编译器将此对象置于处理器的寄存器。(弃用) (C++17 前)static
- 静态或线程存储期和内部链接。extern
- 静态或线程存储期和外部链接。thread_local
- 线程存储期mutable
- 不影响存储期或链接。
程序中的所有对象都具有下列存储期之一:
自动(automatic)存储期。这类对象的存储在外围代码块开始时分配,并在结束时解分配。未声明为 static、extern 或 thread_local 的所有局部对象均拥有此存储期。
静态(static)存储期。这类对象的存储在程序开始时分配,并在程序结束时解分配。这类对象只存在一个实例。所有在命名空间(包含全局命名空间)作用域声明的对象,加上声明带有 static 或 extern 的对象均拥有此存储期。有关拥有此存储期的对象的初始化的细节,见非局部变量与静态局部变量。
线程(thread)存储期。这类对象的存储在线程开始时分配,并在线程结束时解分配。每个线程拥有它自身的对象实例。只有声明为
thread_local
的对象拥有此存储期。thread_local
能与static
或extern
一同出现,它们用于调整链接。关于具有此存储期的对象的初始化的细节,见非局部变量和静态局部变量。动态(dynamic)存储期。这类对象的存储是通过使用动态内存分配函数来按请求进行分配和解分配的。关于具有此存储期的对象的初始化的细节,见 new 表达式。
多态对象
声明或继承了至少一个虚函数的类类型的对象是多态对象。每个多态对象中,实现都会储存额外的信息(在所有现存的实现中,如果没被编译器优化掉的话,这就是一个指针),它被用于进行虚函数的调用,RTTI 功能特性(dynamic_cast
和 typeid
)也用它在运行时确定对象创建时所用的类型,而不管使用它的表达式是什么类型。
对于非多态对象,值的解释方式由使用对象的表达式所确定,这在编译期就已经决定了。
对齐
每个对象类型都具有被称为对齐要求(alignment requirement)的性质,它是一个整数(类型是 std::size_t,总是 2 的幂),表示这个类型的不同对象所能分配放置的连续相邻地址之间的字节数。
可以用 alignof 或 std::alignment_of 来查询类型的对齐要求。可以使用指针对齐函数 std::align 来获取某个缓冲区中经过适当对齐的指针,还可以使用 std::aligned_storage 来获取经过适当对齐的存储区。 |
(C++11 起) |
---|
每个对象类型在该类型的所有对象上强制该类型的对齐要求;可以使用 alignas
来要求更严格的对齐(更大的对齐要求) (C++11 起)。
为了使类中的所有非静态成员都符合对齐要求,会在一些成员后面插入一些填充位。
实参依赖查找
|
关键字
cv(const 与 volatile)类型限定符
暴论: const 在编译时处理, 常量表达式const expression在编译时就可求值
除了函数类型或引用类型以外的任何类型 T
(包括不完整类型),C++ 类型系统中有另外三个独立的类型:const-限定的 T
、volatile-限定的 T
及 const-volatile-限定的 T
。
注意:数组类型被当做与它的元素类型有相同的 cv 限定。
当对象创建时,所用的 cv 限定符(可以是声明中的 声明说明符序列 或 声明符 的一部分,或者是 new 表达式中的 类型标识 的一部分)决定对象的常量性或易变性,如下所示:
- 常量对象——类型是 const-限定的 对象,或常量对象的非可变(mutable)子对象。这种对象不能被修改:直接尝试这么做是编译时错误,而间接尝试这么做(例如通过到非常量类型的引用或指针修改常量对象)的行为未定义。
- 易变对象——类型是 volatile-限定的 对象,或易变对象的子对象,或常量易变对象的可变子对象。每次访问(读或写、调用成员函数等)易变类型之泛左值表达式[1],都当作优化方面可见的副作用(即在单个执行线程内,易变对象访问不能被优化掉,或者与另一先于或后于该易变对象访问的可见副作用进行重排序。这使得易变对象适用于与信号处理函数而非另一执行线程交流,参阅 std::memory_order)。试图通过非易变类型的泛左值访问易变对象(例如,通过到非易变类型的引用或指针)的行为未定义。
- 常量易变对象——类型是 const-volatile-限定的 对象,常量易变对象的非可变子对象,易变对象的常量子对象,或常量对象的非可变易变子对象。同时表现为常量对象与易变对象。
每个 cv 限定符(const 和 volatile)在任何 cv 限定符序列中都最多只能出现一次。例如 const const 和 volatile const volatile 都不是合法的 cv 限定符序列。
非静态成员函数可以带 cv 限定符序列(const、volatile 或 const 和 volatile 的组合)声明,这些限定符在函数声明中的形参列表之后出现。带有不同 cv 限定符(或无限定)的函数具有不同类型,从而可以相互重载。
在有 cv 限定符的函数体内,*this 有同样的 cv 限定,例如在有 const 限定符的成员函数中只能正常地调用其他有 const 限定符的成员函数。(如果应用了 const_cast
,或通过不涉及 this
的访问路径,那么仍然可以调用没有 const 限定符的成员函数。)
constexpr
constexpr
- 指定变量或函数的值可以在常量表达式中出现
constexpr
说明符声明编译时可以对函数或变量求值。这些变量和函数(给定了合适的函数实参的情况下)即可用于需要编译期常量表达式的地方。
声明对象或非静态成员函数 (C++14 前)时使用 constexpr 说明符则同时蕴含 const。声明函数或静态成员变量 (C++17 起)时使用 constexpr 说明符则同时蕴含 inline。如果一个函数或函数模板的某个声明拥有 constexpr
说明符,那么它的所有声明都必须含有该说明符。
decltype
如果实参是指名某个结构化绑定的没有括号的标识表达式,那么 decltype 产生其被引用类型(在关于结构化绑定声明的说明中有所描述)。 |
(C++17 起) |
---|---|
如果实参是指名某个非类型模板形参的没有括号的标识表达式,那么 decltype 生成该模板形参的类型(当该模板形参以占位符类型声明时,类型会先进行任何所需的类型推导)。 |
(C++20 起) |
- 如果实参是其他类型为
T
的任何表达式,且
a) 如果 表达式 的值类别是亡值,将会 decltype
产生 T&&
;
b) 如果 表达式 的值类别是左值,将会 decltype
产生 T&
;
c) 如果 表达式 的值类别是纯右值,将会 decltype
产生 T
。
如果 表达式 是返回类类型纯右值的函数调用,或是右操作数为这种函数调用的逗号表达式,那么不会对该纯右值引入临时量。 | (C++17 前) |
---|---|
如果 表达式 是除了(可带括号的)立即调用以外的 (C++20 起)纯右值,那么不会从该纯右值实质化临时对象:即这种纯右值没有结果对象。 | (C++17 起) |
该类型不需要是完整类型或拥有可用的析构函数,而且类型可以是抽象的。此规则不适用于其子表达式:decltype(f(g())) 中,g() 必须有完整类型,但 f() 不必。
explicit
2. explicit 说明符可以与常量表达式一同使用。函数在且只会在该常量表达式求值为 true 时是显式的。 | (C++20 起) |
---|
explicit 说明符只能在类定义之内的构造函数或转换函数 (C++11 起)的 声明说明符序列 中出现。
static
存储说明
static
说明符只能搭配(函数形参列表外的)对象声明、(块作用域外的)函数声明及匿名联合体声明。当用于声明类成员时,它会声明一个静态成员。当用于声明对象时,它指定静态存储期(除非与 thread_local
协同出现)。在命名空间作用域内声明时,它指定内部链接(名字可从当前翻译单元中的所有作用域使用。 在命名空间作用域声明的下列任何名字均具有内部链接;即未声明为 static
的函数具有外部链接)。
静态(static)存储期。这类对象的存储在程序开始时分配,并在程序结束时解分配。这类对象只存在一个实例。所有在命名空间(包含全局命名空间)作用域声明的对象,加上声明带有 static
或 extern
的对象均拥有此存储期。有关拥有此存储期的对象的初始化的细节,见非局部变量与静态局部变量。
非局部变量, 所有具有静态存储期的非局部变量的初始化会作为程序启动的一部分在 main 函数的执行之前进行(除非被延迟,见下文)。所有具有线程局部存储期的非局部变量的初始化会作为线程启动的一部分进行,按顺序早于线程函数的执行开始。对于这两种变量,初始化发生于两个截然不同的阶段:
有两种静态初始化的形式:
\1) 如果可能,那么应用常量初始化。(设置静态变量的初值为编译时常量。)
\2) 否则非局部静态及线程局域变量会被零初始化。
实践中:
- 常量初始化通常在编译期进行。预先被计算的对象表示会作为程序映像的一部分存储下来。如果编译器没有这样做,那么它仍然必须保证该初始化发生早于任何动态初始化。
- 零初始化的变量将被置于程序映像的
.bss
段,它不占据磁盘空间,并在加载程序时由操作系统以零填充。
静态局部变量
在块作用域声明且带有 static
或 thread_local
(C++11 起) 说明符的变量拥有静态或线程 (C++11 起)存储期,但在控制首次经过它的声明时才会被初始化(除非它被零初始化或常量初始化,这可以在首次进入块前进行)。在其后所有的调用中,声明都会被跳过。
如果初始化抛出异常,那么不认为变量被初始化,且控制下次经过该声明时将再次尝试初始化。
如果初始化递归地进入正在初始化的变量的块,那么行为未定义。
如果多个线程试图同时初始化同一静态局部变量,那么初始化严格发生一次(类似的行为也可对任意函数以 std::call_once 来达成)。注意:此功能特性的通常实现均使用双检查锁定模式的变体,这使得对已初始化的局部静态变量检查的运行时开销减少为单次非原子的布尔比较。 | (C++11 起) |
---|
块作用域静态变量的析构函数在初始化已成功的情况下在程序退出时被调用。
在相同内联函数(可以是隐式内联)的所有定义中,函数局部的静态对象均指代在一个翻译单元中定义的同一对象,只要函数拥有外部链接。
静态成员
inline
inline 说明符,在用于函数的 声明说明符序列 时,将函数声明为一个 内联(inline)函数。
整个定义都在 class/struct/union 的定义内且被附着到全局模块 (C++20 起)的函数是隐式的内联函数,无论它是成员函数还是非成员 friend 函数。
声明有 constexpr 的函数是隐式的内联函数。弃置的函数是隐式的内联函数:它的(弃置)定义可以在多于一个翻译单元中出现。 | (C++11 起) |
---|---|
inline 说明符,在用于具有静态存储期的变量(静态类成员或命名空间作用域变量)的 声明说明符序列 时,将变量声明为内联变量。声明为 constexpr 的静态成员变量(但不是命名空间作用域变量)是隐式的内联变量。 | (C++17 起) |
解释
内联函数或内联变量 (C++17 起)具有下列性质:
- 内联函数或变量 (C++17 起)的定义必须在访问它的翻译单元中可达(不一定要在访问点前)。
- 带外部连接的 inline 函数或变量 (C++17 起)(例如不声明为 static)拥有下列额外属性:
- 内联函数或变量 (C++17 起)在程序中可以有多于一次定义,只要每个定义都出现在不同翻译单元中(对于非静态的内联函数和变量 (C++17 起))且所有定义等同即可。例如,内联函数或内联变量 (C++17 起)可以在被多个源文件所 #include 的头文件中定义。
- 它必须在每个翻译单元中都被声明为 inline 。
- 它在每个翻译单元中都拥有相同的地址。
在内联函数中,
- 所有函数定义中的函数局部静态对象在所有翻译单元间共享(它们都指代相同的在某一个翻译单元中定义的对象)
- 所有函数定义中所定义的类型同样在所有翻译单元中相同。
命名空间作用域的内联 const 变量默认具有外部连接(这点与非内联非 volatile 的有 const 限定的变量不同) | (C++17 起) |
---|
inline 关键词的本意是作为给优化器的指示器,以指示优先采用函数的内联替换而非进行函数调用,即并不执行将控制转移到函数体内的函数调用 CPU 指令,而是代之以执行函数体的一份副本而无需生成调用。这会避免函数调用的开销(传递实参及返回结果),但它可能导致更大的可执行文件,因为函数体必须被复制多次。
因为关键词 inline 的含义是非强制的,编译器拥有对任何未标记为 inline 的函数使用内联替换的自由,和对任何标记为 inline 的函数生成函数调用的自由。这些优化选择不改变上述关于多个定义和共享静态变量的规则。
由于关键词 inline 对于函数的含义已经变为“容许多次定义”而不是“优先内联”,因此这个含义也扩展到了变量。 | (C++17 起) |
---|
注解
如果具有外部连接的内联函数或变量 (C++17 起)在不同翻译单元中的定义不同,那么行为未定义。
inline 说明符不能用于块作用域内(函数内部)的函数或变量 (C++17 起)声明。
inline 说明符不能重声明在翻译单元中已定义为非内联的函数或变量 (C++17 起)。
隐式生成的成员函数和任何在它的首条声明中声明为预置的成员函数,与任何其他在类定义内定义的函数一样是内联的。
如果一个内联函数在不同翻译单元中被声明,那么它的默认实参的积累集合必须在每个翻译单元的末尾相同。
在 C 中,内联函数不必在每个翻译单元声明为 inline(最多一个可以是非 inline 或 extern inline),函数定义不必相同(但如果程序依赖于调用的是哪个函数则行为未指明),且函数局部的静态变量在同一函数的不同定义间不同。
关于内联静态成员的额外规则见静态数据成员。内联变量消除了将 C++ 代码打包为只有头文件的库的主要障碍。 | (C++17 起) |
---|
表达式
初等表达式
初等表达式包括以下各项:
this
- 字面量(例如 2 或 "Hello, world")
- 标识表达式,包括
- 经过适当声明的无限定的标识符(例如 n 或 cout),
- 经过适当声明的有限定的标识符(例如 std::string::npos),以及
- 在声明符中将要声明的标识符
lambda 表达式 | (C++11 起) |
---|---|
折叠表达式 | (C++17 起) |
requires 表达式 | (C++20 起) |
括号中的任何表达式也被归类为初等表达式:这确保了括号具有比任何运算符更高的优先级。括号保持值、类型和值类别不变。
值 value
每个 C++ 表达式(带有操作数的操作符、字面量、变量名等)可按照两种独立的特性加以辨别:类型和值类别 (value category)。每个表达式都具有某种非引用类型,且每个表达式只属于三种基本值类别中的一种:纯右值 (prvalue)、亡值 (xvalue)、左值 (lvalue)。
泛左值 (glvalue)
(“泛化 (generalized)”的左值)是一个表达式,其值可确定某个对象或函数的标识;
泛左值表达式包括左值、亡值。
性质:
纯右值 (prvalue)
(“纯 (pure)”的右值)是求值符合下列之一的表达式:
- 计算某个运算符的操作数的值(这种纯右值没有结果对象)
- 初始化某个对象(称这种纯右值有一个结果对象)。
下列表达式是纯右值表达式:
- (除了字符串字面量之外的)字面量,例如 42、true 或 nullptr;
- 返回类型是非引用的函数调用或重载运算符表达式,例如 str.substr(1, 2)、str1 + str2 或 it++;
- a++ 和 a--,内建的后置自增与后置自减表达式;
- a + b、a % b、a & b、a << b,以及其他所有内建的算术表达式;
- a && b、a || b、!a,内建的逻辑表达式;
- a < b、a == b、a >= b 以及其他所有内建的比较表达式;
- &a,内建的取地址表达式;
- a.m,对象成员表达式,其中
m
是成员枚举项或非静态成员函数[2]; - p->m,内建的指针成员表达式,其中
m
是成员枚举项或非静态成员函数[2]; - a.*mp,对象的成员指针表达式,其中
mp
是成员函数指针[2]}; - p->*mp,内建的指针的成员指针表达式,其中
mp
是成员函数指针[2]; - a, b,内建的逗号表达式,其中 b 是右值;
- a ? b : c,对某些 b 和 c 的三元条件表达式(细节见定义);
- 转换到非引用类型的转型表达式,例如 static_cast
(x)、std::string 或 (int)42; this
指针;???- 枚举项;
- 具有标量类型的非类型模板形参;
lambda 表达式,例如 { return x * x; }; | (C++11 起) |
---|
requires 表达式,例如 requires (T i) { typename T::type; };概念的特化,例如 std::equality_comparable |
(C++20 起) |
---|
性质:
- 与右值相同(见下文)。
- 纯右值不具有多态:它所标识的对象的动态类型始终是该表达式的类型。
- 非类非数组的纯右值不能有 cv 限定,除非它被实质化以绑定到 cv 限定类型的引用 (C++17 起)。(注意:函数调用或转型表达式可能生成非类的 cv 限定类型的纯右值,但它的 cv 限定符通常被立即剥除。)
- 纯右值不能具有不完整类型(除了类型 void(见下文),或在
decltype
说明符中使用之外) - 纯右值不能具有抽象类类型或它的数组类型。
亡值 (xvalue)
(“将亡 (expiring)”的值)是代表它的资源能够被重新使用的对象或位域的泛左值;
下列表达式是亡值表达式:
- a.m,对象成员表达式,其中
a
是右值且m
是对象类型的非静态数据成员; - a.*mp,对象的成员指针表达式,其中 a 是右值且
mp
是数据成员指针; - a ? b : c,对某些 b 和 c 的三元条件表达式(细节见定义);
返回类型是对象的右值引用的函数调用或重载运算符表达式,例如 std::move(x);a[n],内建的下标表达式,它的操作数之一是数组右值;转换到对象的右值引用类型的转型表达式,例如 static_cast<char&&>(x); | (C++11 起) |
---|---|
在临时量实质化后,任何指代该临时对象的表达式。 | (C++17 起) |
性质:
- 与右值相同(见下文)。
- 与泛左值相同(见下文)。
特别是,与所有的右值类似,亡值可以绑定到右值引用上,而且与所有的泛左值类似,亡值可以是多态的,而且非类的亡值可以有 cv 限定。
左值 (lvalue)
(如此称呼的历史原因是,左值可以在赋值表达式的左边出现)是非亡值的泛左值;
下列表达式是左值表达式:
- 变量、函数、模板形参对象 (C++20 起)或数据成员的名字,不论类型,例如 std::cin 或 std::endl。即使变量的类型是右值引用,由它的名字构成的表达式仍是左值表达式;
- 返回类型是左值引用的函数调用或重载运算符表达式,例如 std::getline(std::cin, str)、std::cout << 1、str1 = str2 或 ++it;
- a = b,a += b,a %= b,以及所有其他内建的赋值及复合赋值表达式;
- ++a 和 --a,内建的前置自增与前置自减表达式;
- *p,内建的间接寻址表达式;
- a[n] 和 n[a],内建的下标表达式,当
a[n]
中的一个操作数是数组左值时 (C++11 起); - a.m,对象成员表达式,除了
m
是成员枚举项或非静态成员函数,或者 a 是右值而m
是对象类型的非静态数据成员的情况; - p->m,内建的指针成员表达式,除了
m
是成员枚举项或非静态成员函数的情况; - a.*mp,对象的成员指针表达式,其中 a 是左值且
mp
是数据成员指针; - p->*mp,内建的指针的成员指针表达式,其中
mp
是数据成员指针; - a, b,内建的逗号表达式,其中 b 是左值;
- a ? b : c,对某些 b 和 c 的三元条件表达式(例如,当它们都是同类型左值时,但细节见定义);
- 字符串字面量,例如 "Hello, world!";
- 转换到左值引用类型的转型表达式,例如 static_cast<int&>(x);
- 具有左值引用类型的非类型模板形参;
返回类型是到函数的右值引用的函数调用表达式或重载的运算符表达式;转换到函数的右值引用类型的转型表达式,如 static_cast<void (&&)(int)>(x)。 | (C++11 起) |
---|
性质:
- 与泛左值相同(见下文)。
- 可以通过内建的取址运算符取左值的地址:&++i[1] 及 &std::endl 是合法表达式。
- 可修改的左值可用作内建赋值和内建复合赋值运算符的左操作数。
- 左值可以用来初始化左值引用;这会将一个新名字关联给该表达式所标识的对象。
右值 (rvalue)
(如此称呼的历史原因是,右值可以在赋值表达式的右边出现)是纯右值或者亡值。
右值表达式包括纯右值、亡值。
性质:
- 右值不能由内建的取址运算符取地址:&int()、&i++[3]、&42 及 &std::move(x) 是非法的。
- 右值不能用作内建赋值运算符及内建复合赋值运算符的左操作数。
- 右值可以用来初始化 const 左值引用,这种情况下该右值所标识的对象的生存期被延长到该引用的作用域结尾。
右值可以用来初始化右值引用,这种情况下该右值所标识的对象的生存期被延长到该引用的作用域结尾。当被用作函数实参且该函数有两种重载可用,其中之一接受右值引用的形参而另一个接受 const 的左值引用的形参时,右值将被绑定到右值引用的重载之上(从而,当复制与移动构造函数均可用时,以右值实参将调用它的移动构造函数,复制和移动赋值运算符与此类似)。 | (C++11 起) |
---|
c++11
随着移动语义引入到 C++11 之中,值类别被重新进行了定义,以区别表达式的两种独立的性质[5]:
- 拥有身份 (identity):可以确定表达式是否与另一表达式指代同一实体,例如通过比较它们所标识的对象或函数的(直接或间接获得的)地址;
- 可被移动:移动构造函数、移动赋值运算符或实现了移动语义的其他函数重载能够绑定于这个表达式。
C++11 中:
- 拥有身份且不可被移动的表达式被称作左值 (lvalue)表达式;
- 拥有身份且可被移动的表达式被称作亡值 (xvalue)表达式;
- 不拥有身份且可被移动的表达式被称作纯右值 (prvalue)表达式;
- 不拥有身份且不可被移动的表达式无法使用[6]。
拥有身份的表达式被称作“泛左值 (glvalue) 表达式”。左值和亡值都是泛左值表达式。
可被移动的表达式被称作“右值 (rvalue) 表达式”。纯右值和亡值都是右值表达式。
求值顺序
求值任何表达式的任何部分,包括求值函数参数的顺序都未指明(除了下列的一些例外)。编译器能以任何顺序求值任何操作数和其他子表达式,并且可以在再次求值同一表达式时选择另一顺序。
C++ 中无从左到右或从右到左求值的概念。这不会与运算符的从左到右及从右到左结合性混淆:表达式 a() + b() + c() 由于 operator+ 的从左到右结合性被分析成 (a() + b()) + c(),但在运行时可以首先、最后或者在 a() 和 b() 之间对 c() 求值:
规则
\1) 完整表达式的每次值计算和副作用都按顺序早于下一个完整表达式的每个值计算和副作用。
\2) 任何运算符的各操作数的值计算(但非副作用)都按顺序早于该运算符结果的值计算(但非副作用)。
\3) 调用函数时(无论函数是否内联,且无论是否使用显式函数调用语法),与任何实参表达式或与指代被调用函数的后缀表达式关联的每个值计算和副作用,都按顺序早于被调用函数体内的每个表达式或语句的执行。
\4) 内建后自增与后自减运算符的值计算按顺序早于它的副作用。
\5) 内建前自增与前自减运算符的副作用按顺序早于它的值计算(作为由复合赋值的定义所致的隐含规则)。
\6) 内建逻辑与(AND)运算符 && 和内建逻辑或(OR)运算符 || 的第一(左)操作数的每个值计算和副作用,按顺序早于第二(右)操作数的每个值计算和副作用。
\7) 与条件运算符 ?: 中的第一个表达式关联的每个值计算和副作用,都按顺序早于与第二或第三表达式关联的每个值计算和副作用。
\8) 内建赋值运算符和所有内建复合赋值运算符的副作用(修改左参数),都按顺序晚于左右参数的值计算(但非副作用),且按顺序早于赋值表达式的值计算(即早于返回指代被修改对象的引用之时)。
\9) 内建逗号运算符 , 的第一个(左)参数的每个值计算和副作用都按顺序早于第二个(右)参数的每个值计算和副作用。
\10) 列表初始化中,在大括号中用逗号分隔的任何给定的初始化器子句的每个值计算和副作用都按顺序早于逗号后的任何给定的初始化器子句的每个值计算和副作用
\11) 如果某个函数调用既不按顺序早于又不按顺序晚于另一函数调用,那么它们是顺序不确定的(程序必须表现为如同组成不同函数调用的 CPU 指令决不会交错,即使函数被内联也是如此)。
规则 11 有一个例外:在 std::execution::par_unseq 执行策略下执行的标准库算法所作的函数调用是无顺序的,并且可以任意交错。 |
(C++17 起) |
---|
\12) 对分配函数(operator new
)的调用相对于 new 表达式中构造函数参数的求值来说,是顺序不确定的 (C++17 前)按顺序早于它 (C++17 起)。
\13) 从函数返回时,作为求值函数调用结果的临时量的复制初始化按顺序早于在 return 语句的操作数末尾处对所有临时量的销毁,而这些销毁进一步按顺序早于对环绕 return 语句的块的所有局部变量的销毁。
14) 函数调用表达式中,指名函数的表达式按顺序早于每个参数表达式和每个默认实参。 15) 函数调用表达式中,每个形参的初始化的值计算和副作用相对于任何其他形参的初始化的值计算和副作用是顺序不确定的。 16) 用运算符写法进行调用时,每个重载的运算符都会遵循它所重载的内建运算符的定序规则。 17) 下标表达式 E1[E2] 中,E1 的每个值计算和副作用都按顺序早于 E2 的每个值计算和副作用。 18) 成员指针表达式 E1.E2 或 E1->E2 中,E1 的每个值计算和副作用都按顺序早于 E2 的每个值计算和副作用(除非 E1 的动态类型不含 E2 所指的成员)。 19) 移位运算符表达式 E1 << E2 和 E1 >> E2 中,E1 的每个值计算和副作用都按顺序早于 E2 的每个值计算和副作用。 20) 每个简单赋值表达式 E1 = E2 和每个复合赋值表达式 E1 @= E2 中,E2 的每个值计算和副作用都按顺序早于 E1 的每个值计算和副作用。 21) 带括号的初始化器中的逗号分隔的表达式列表中的每个表达式,如同函数调用一般求值(顺序不确定)。 |
(C++17 起) |
---|