以下的语言机能主要用来提升某些性能表现,像是存储器或是速度上的表现。
右值引用和 move 语义
在 C++(www.cppentry.com)03及之前的标准,临时对象(称为右值"R-values",位于赋值操作符之右)无法被改变,在 C 中亦同(且被视为无法和 const T& 做出区分)。尽管在某些情况下临时对象的确会被改变,甚至也被视为是一个有用的漏洞。
C++(www.cppentry.com)11 增加一个新的非常数引用(reference)类型,称作右值引用(R-value reference),标记为T &&
。右值引用所引用的临时对象可以在该临时对象被初始化之后做修改,这是为了允许 move 语义。C++(www.cppentry.com)03 性能上被长期被诟病的其中之一,就是其耗时且不必要的深度拷贝。深度拷贝会发生在当对象是以传值的方式传递。举例而言,std::vector<T>
是内部保存了 C-style 数组的一个包装,如果一个std::vector<T>的临时对象被建构或是从函数返回,要将其存储只能通过生成新的std::vector<T>并且把该临时对象所有的数据复制进去。该临时对象和其拥有的内存会被摧毁。(为了讨论上的方便,这里忽略返回值优化)在 C++(www.cppentry.com)11,一个std::vector
的 "move 建构子" 对某个vector的右值引用可以单纯地从右值复制其内部 C-style 数组的指针到新的 vector,然后留下空的右值。这个操作不需要数组的复制,而且空的暂时对象的解构也不会摧毁存储器。传回vector暂时对象的函数只需要传回std::vector<T>&&。如果vector没有 move 建构子,那么复制建构子将被调用,以const std::vector<T> &的正常形式。 如果它确实有 move 建构子,那么就会调用 move 建构子,这能够免除大幅的存储器配置。基于安全的理由,具名的变量将永远不被认定为右值,即使它是被如此声明的;为了获得右值必须使用 std::move<T>()
。bool is_r_value(int &&) { return true; }
bool is_r_value(const int &) { return false; }
void test(int && i)
{
is_r_value(i); // i 為具名變數,即使被宣告成右值也不會被認定是右值。
is_r_value(std::move<int>(i)); // 使用 std::move<T>() 取得右值。
}
由于右值引用的用语特性以及对于左值引用(L-value references;regular references)的某些用语修正,右值引用允许开发者提供完美转发 (perfect function forwarding)。当与变长参数模板结合,这项能力允许函数模板能够完美地转送引数给其他接受这些特定引数的函数。最大的用处在于转送建构子参数,创造出能够自动为这些特定引数调用正确建构式的工厂函数(factory function)。
泛化的常数表示式
C++(www.cppentry.com) 本来就已具备常数表示式(constant expression)的概念。像是 3+4
总是会产生相同的结果并且没有任何的副作用。常数表示式对编译器来说是优化的机会,编译器时常在编译期运行它们并且将值存入程序中。同样地,在许多场合下,C++(www.cppentry.com) 规格要求使用常数表示式。例如在数组大小的定义上,以及枚举值(enumerator values)都要求必须是常数表示式。然而,常数表示式总是在遇上了函数调用或是对象建构式时就终结。所以像是以下的例子是不合法的:
int GetFive() {return 5;}
int some_value[GetFive() + 5];// 欲產生 10 個整數的陣列。 不合法的 C++(www.cppentry.com) 寫法
这不是合法的 C++(www.cppentry.com),因为 GetFive() + 5
并不是常数表示式。编译器无从得知 GetFive 实际上在运行期是常数。理论上而言,这个函数可能会影响全局变量,或者调用其他的非运行期(non-runtime)常数函数等。C++(www.cppentry.com)11引进关键字 constexpr
允许用户保证函数或是对象建构式是编译期常数。以上的例子可以被写成像是下面这样:constexpr int GetFive() {return 5;}
int some_value[GetFive() + 5];// 欲產生 10 個整數的陣列。合法的C++(www.cppentry.com)11寫法
这使得编译器能够了解并去验证 GetFive
是个编译期常数。对函数使用 constexpr
在函数可以做的事上面加上了非常严格的条件。首先,该函数的回返值类型不能为 void。第二点,函数的内容必须依照 "return expr" 的形式。第三点,在引数取代后,expr 必须是个常数表示式。这些常数表示式只能够调用其他被定义为 constexpr 的函数,或是其他常数表示式的数据变量。 最后一点,有着这样标签的函数直到在该编译单元内被定义之前是不能够被调用的。变量也可以被定义为常数表示式值:
constexpr double forceOfGravity = 9.8;
constexpr double moonGravity = forceOfGravity / 6.0;
常数表示式的数据变量是隐式的常数。他们可以只存储常数表示式或常数表示式建构式的结果。
为了从用户自定类型(user-defined type)建构常数表示式的数据变量,建构式也可以被声明成 constexpr
。与常数表示式函数一样,常数表示式的建构式必须在该编译单元内使用之前被定义。他必须有着空的函数本体。它必须用常数表示式初始化他的成员(member)。而这种类型的解构式应当是无意义的(trivial),什么事都不做。复制 constexpr 建构起来的类型也应该被定义为 constexpr
,这样可以让他们从常数表示式的函数以值传回。类型的任何成员函数,像是复制建构式、重载的操作符等等,只要他们符合常数表示式函数的定义,都可以被声明成 constexpr。这使得编译器能够在编译期进行类型的复制、对他们施行运算等等。常数表示式函数或建构式,可以以非常数表示式(non-constexpr)参数唤起。就如同 constexpr 整数字面值能够指派给 non-constexpr 变量,constexpr 函数也可以接受 non-constexpr 参数,其结果存储于 non-constexpr 变量。constexpr 关键字只有当表示式的成员都是 constexpr,才允许编译期常数性的可能。
对POD定义的修正
在标准C++(www.cppentry.com),一个结构(struct)为了能够被当成plain old data (POD),必须遵守几条规则。有很好的理由使我们想让大量的类型符合这种定义,符合这种定义的类型能够允许产生与C兼容的对象布局(object layout)。然而,C++(www.cppentry.com)03的规则太严苛了。
C++(www.cppentry.com)11将会放宽关于POD的定义。
当class/struct是极简的(trivial)
、属于标准布局(standard-layout),以及他的所有非静态(non-static)成员都是POD时,会被视为POD。一个极简的类型或结构符合以下定义:
- 极简的默认建构式。这可以使用默认建构式语法,例如SomeConstructor() = default;
极简的复制建构式,可使用默认语法(default syntax)极简的赋值操作符,可使用默认语法(default syntax)极简的解构式,不可以是虚拟的(virtual)一个标准布局(standard-layout)的类型或结构符合以下定义:
- 只有非静态的(non-static)数据成员,且这些成员也是符合标准布局的类型
- 对所有non-static成员有相同的访问控制(public, private, protected)
- 没有虚函数
- 没有虚拟基类
- 只有符合标准布局的基类
- 没有和第一个定义的non-static成员相同类型的基类
- 若非没有带有non-static成员的基类,就是最底层(继承最末位)的类型没有non-static数据成员而且至多一个带有non-static成员的基类。基本上,在该类型的继承体系中只会有一个类型带有non-static成员。