这些机能提供了C++(www.cppentry.com)语言能够做一些事情是以前所不能达成的,或是在以前需要繁琐的写法、要求一些不可移植的程序库。
变长参数模板
在 C++(www.cppentry.com)11 之前, 不论是模板类或是模板函数,都只能按其被声明时所指定的样子,接受一组固定数目的模板实参; C++(www.cppentry.com)11 加入新的表示法,允许任意个数、任意类别的模板实参,不必在定义时将实参的个数固定。
template<typename... Values> class tuple;
模板类 tuple
的对象,能接受不限个数的 typename 作为它的模板形参:class tuple<int, std::vector<int>, std::map<std::string, std::vector<int>>> someInstanceName;
实参的个数也可以是 0,所以 class tuple<> someInstanceName
这样的定义也是可以的。若不希望产生实参个数为 0 的变长参数模板,则可以采用以下的定义:
template<typename First, typename... Rest> class tuple;
变长参数模板也能运用到模板函数上。 传统 C 中的 printf 函数,虽然也能达成不定个数的形参的调用,但其并非类别安全。 以下的样例中,C++(www.cppentry.com)11 除了能定义类别安全的变长参数函数外,还能让类似 printf 的函数能自然地处理非自带类别的对象。 除了在模板实参中能使用...
表示不定长模板实参外,函数实参也使用同样的表示法代表不定长实参。template<typename... Params> void printf(const std::string &strFormat, Params... parameters);
其中,Params
与 parameters 分别代表模板与函数的变长参数集合, 称之为实参包 (parameter pack)。实参包必须要和运算符"..."搭配使用,避免语法上的歧义。变长参数模板中,变长参数包无法如同一般实参在类或函数中使用; 因此典型的手法是以递归的方法取出可用实参,参看以下的 C++(www.cppentry.com)11 printf
样例:void printf(const char *s)
{
while (*s)
{
if (*s == '%' && *(++s) != '%')
throw std::runtime_error("invalid format string: missing arguments");
std::cout << *s++;
}
}
template<typename T, typename... Args>
void printf(const char* s, T value, Args... args)
{
while (*s)
{
if (*s == '%' && *(++s) != '%')
{
std::cout << value;
printf(*s ++s : s, args...); // 即便当 *s == 0 也会产生调用,以检测更多的类型参数。
return;
}
std::cout << *s++;
}
throw std::logic_error("extra arguments provided to printf");
}
printf 会不断地递归调用自身:函数实参包 args...
在调用时, 会被模板类别匹配分离为 T value和 Args... args。 直到 args... 变为空实参,则会与简单的 printf(const char *s) 形成匹配,退出递归。另一个例子为计算模板实参的个数,这里使用相似的技巧展开模板实参包 Args...
:template<>
struct count<> {
static const int value = 0;
};
template<typename T, typename... Args>
struct count<T, Args...> {
static const int value = 1 + count<Args...>::value;
};
虽然没有一个简洁的机制能够对变长参数模板中的值进行迭代,但使用运算符"..."还能在代码各处对实参包施以更复杂的展开操作。举例来说,一个模板类的定义:
template <typename... BaseClasses> class ClassName : public BaseClasses...
{
public:
ClassName (BaseClasses&&... baseClasses) : BaseClasses(baseClasses)... {}
}
BaseClasses...
会被展开成类型 ClassName 的基底类; ClassName 的构造函数需要所有基类的左值引用,而每一个基类都是以传入的实参做初始化 (BaseClasses(baseClasses)...)。在函数模板中,变长参数可以和左值引用搭配,达成形参的完美转送 (perfect forwarding):
template<typename TypeToConstruct> struct SharedPtrAllocator
{
template<typename... Args> std::shared_ptr<TypeToConstruct> ConstructWithSharedPtr(Args&&... params)
{
return tr1::shared_ptr<TypeToConstruct>(new TypeToConstruct(std::forward<Args>(params)...));
}
}
实参包 parms
可展开为 TypeToConstruct 构造函数的形参。 表达式std::forward<Args>(params) 可将形参的类别信息保留(利用右值引用),传入构造函数。 而运算符"..."则能将前述的表达式套用到每一个实参包中的实参。这种工厂函数(factory function)的手法, 使用 std::shared_ptr 管理配置对象的存储器,避免了不当使用所产生的存储器泄漏(memory leaks)。此外,变长参数的数量可以藉以下的语法得知:
template<typename ...Args> struct SomeStruct
{
static const int size = sizeof...(Args);
}
SomeStruct<Type1, Type2>::size
是 2,而 SomeStruct<>::size 会是 0。 (sizeof...(Args) 的结果是编译期常数。)
新的字符串字面值
标准C++(www.cppentry.com)提供了两种字符串字面值。第一种,包含有双引号,产生以空字符结尾的const char
数组。第二种有着前标L,产生以空字符结尾的const wchar_t数组,其中wchar_t代表宽字符。对于Unicode编码的支持尚付阙如。为了加强C++(www.cppentry.com)编译器对Unicode的支持,类别char
的定义被修改为其大小至少能够存储UTF-8的8位编码,并且能够容纳编译器的基本字符集的任何成员。C++(www.cppentry.com)11 将支持三种Unicode编码方式:UTF-8,UTF-16,和UTF-32。除了上述char
定义的变更, C++(www.cppentry.com)11将增加两种新的字符类别:char16_t和char32_t。它们各自被设计用来存储UTF-16 以及UTF-32的字符。以下展示如何产生使用这些编码的字符串字面值:
u8"I'm a UTF-8 string."
u"This is a UTF-16 string."
U"This is a UTF-32 string."
第一个字符串的类别是通常的const char[]
;第二个字符串的类别是const char16_t[];第三个字符串的类别是const char32_t[]。当创建Unicode字符串字面值时,可以直接在字符串内插入Unicode codepoints。C++(www.cppentry.com)11提供了以下的语法:
u8"This is a Unicode Character: \u2018."
u"This is a bigger Unicode Character: \u2018."
U"This is a Unicode Character: \u2018."
在'\u'之后的是16个比特的十六进制数值;它不需要'0x'的前标。识别字'\u'代表了一个16位的Unicode codepoint;如果要输入32位的codepoint,使用'\U'和32个比特的十六进制数值。只有有效的Unicode codepoints能够被输入。举例而言,codepoints在范围U+D800—U+DFFF之间是被禁止的,它们被保留给UTF-16编码的surrogate pairs。
有时候避免手动将字符串换码也是很有用的,特别是在使用XML文件或是一些脚本语言的字面值的时候。 C++(www.cppentry.com)11将提供raw(未加工的)字符串字面值:
R"(The String Data \ Stuff " )"
R"delimiter(The String Data \ Stuff " )delimiter"
在第一个例子中,任何包含在(
)括号(标准已经从[]改为())当中的都是字符串的一部分。其中"和\字符不需要经过跳脱(escaped)。在第二个例子中,"delimiter(开始字符串,只有在遇到)delimiter"才代表退出。其中delimiter可以是任意的字符串,能够允许用户在未加工的字符串字面值中使用)字符。 未加工的字符串字面值能够和宽字面值或是Unicode字面值结合:u8R"XXX(I'm a "raw UTF-8" string.)XXX"
uR"*@(This is a "raw UTF-16" string.)*@"
UR"(This is a "raw UTF-32" string.)"
用户自定义的字面值
标准C++(www.cppentry.com)提供了数种字面值。字符"12.5"是能够被编译器解释为数值12.5的double
类别字面值。然而,加上"f"的后置,像是"12.5f",则会产生数值为12.5的float类别字面值。在C++(www.cppentry.com)规范中字面值的后置是固定的,而且C++(www.cppentry.com)代码并不允许创立新的字面后置。C++(www.cppentry.com)1x 开放用户定义新的字面修饰符(literal modifier),利用自定义的修饰符完成由字面值建构对象。
字面值转换可以区分为两个阶段:转换前与转换后 (raw 与 cooked)。 转换前的字面值指特定字符串行,而转换后的字面值则代表另一种类别。 如字面值1234
,转换前的字面值代表'1', '2', '3', '4' 的字符串行; 而转换后,字面值代表整数值1234。 另外,字面值0xA转换前是串行'0', 'x', 'A';转换后代表整数值 10。
多任务存储器模型
参见:内存模型(computing)C++(www.cppentry.com)标准委员会计划统一对多线程编程(www.cppentry.com)的支持。
这将涉及两个部分:第一、设计一个可以使多个线程在一个进程中共存的内存模型;第二、为线程之间的交互提供支持。第二部分将由程序库提供支持,更多请看线程支持。
在多个线程可能会访问相同内存的情形下,由一个内存模型对它们进行调度是非常有必要的。遵守模型规则的程序是被保证正确运行的,但违反规则的程序会发生不可预料的行为,这些行为依赖于编译器的优化和存储器一致性的问题。
thread-local的存储期限
在多线程环境下,让各线程拥有各自的变量是很普遍的。这已经存在于函数的区域变量,但是对于全局和静态变量都还不行。
新的thread_local
存储期限(在现行的static、dynamic和automatic之外)被作为下个标准而提出。线程区域的存储期限会借由存储指定字thread_local来表明。static对象(生命周期为整个程序的运行期间)的存储期限可以被thread-local给替代。就如同其他使用static存储期的变量,thread-local对象能够以构造函数初始化并以解构式摧毁。
使用或禁用对象的默认函数
在传统C++(www.cppentry.com)中,若用户没有提供, 则编译器会自动为对象生成默认构造函数(default constructor)、 复制构造函数(copy constructor),赋值操作符(copy assignment operatoroperator=
) 以及解构式(destructor)。另外,C++(www.cppentry.com)也为所有的类定义了数个全局运算符(如operator delete及operator new)。当用户有需要时,也可以提供自定义的版本改写上述的函数。问题在于原先的c++无法精确地控制这些默认函数的生成。 比方说,要让类型不能被拷贝,必须将复制构造函数与赋值操作符声明为private,并不去定义它们。 尝试使用这些未定义的函数会导致编译期或连结期的错误。 但这种手法并不是一个理想的解决方案。
此外,编译器产生的默认构造函数与用户定义的构造函数无法同时存在。 若用户定义了任何构造函数,编译器便不会生成默认构造函数; 但有时同时带有上述两者提供的构造函数也是很有用的。 目前并没有显式指定编译器产生默认构造函数的方法。
C++(www.cppentry.com)11 允许显式地表明采用或拒用编译器提供的自带函数。例如要求类型带有默认构造函数,可以用以下的语法:
struct SomeType
{
SomeType() = default; // 預設建構式的顯式聲明
SomeType(OtherType value);
};
另一方面,也可以禁止编译器自动产生某些函数。如下面的例子,类型不可复制:
struct NonCopyable
{
NonCopyable & operator=(const NonCopyable&) = delete;
NonCopyable(const NonCopyable&) = delete;
NonCopyable() = default;
};
禁止类型以operator new
配置存储器:struct NonNewable
{
void *operator new(std::size_t) = delete;
};
此种对象只能生成于 stack 中或是当作其他类型的成员,它无法直接配置于 heap 之中,除非使用了与平台相关,不可移植的手法。 (使用 placement new 运算符虽然可以在用户自配置的存储器上调用对象构造函数,但在此例中其他形式的 new 运算符一并被上述的定义 屏蔽("name hiding"),所以也不可行。)
= delete
的声明(同时也是定义)也能适用于非自带函数, 禁止成员函数以特定的形参调用:struct NoDouble
{
void f(int i);
void f(double) = delete;
};
若尝试以 double 的形参调用 f()
,将会引发编译期错误, 编译器不会自动将 double 形参转型为 int 再调用f()。 若要彻底的禁止以非int的形参调用f(),可以将= delete与模板相结合:struct OnlyInt
{
void f(int i);
template<class T> void f(T) = delete;
};
long long int类别
在 32 位系统上,一个 long long int
是保有至少 64 个有效比特的整数类别。C99 将这个类别引入了标准 C 中,目前大多数的 C++(www.cppentry.com) 编译器也支持这种类别。C++(www.cppentry.com)11 将把这种类别添加到标准 C++(www.cppentry.com) 中。
静态assertion
C++(www.cppentry.com)提供了两种方法测试assertion(声明):宏assert
以及前处理器指令#error。但是这两者对于模版来说都不合用。宏在运行期测试assertion,而前处理器指令则在前置处理时测试assertion,这时候模版还未能实例化。所以它们都不适合来测试牵扯到模板实参的相关特性。新的机能会引进新的方式可以在编译期测试assertion,只要使用新的关键字static_assert
。 声明采取以下的形式:static_assert( constant-expression, error-message ) ;
这里有一些如何使用static_assert
的例子:static_assert( 3.14 < GREEKPI && GREEKPI < 3.15, "GREEKPI is inaccurate!" ) ;
template< class T >
struct Check
{
static_assert( sizeof(int) <= sizeof(T), "T is not big enough!" ) ;
} ;
当常数表达式值为false
时,编译器会产生相应的错误消息。第一个例子是前处理器指令#error的替代方案;第二个例子会在每个模板类型Check生成时检查assertion。静态assertion在模板之外也是相当有用的。例如,某个算法的实现依赖于long long
类别的大小比int还大,这是标准所不保证的。 这种假设在大多数的系统以及编译器上是有效的,但不是全部。
允许sizeof操作符作用在类型的数据成员上,无须明确的对象
在标准C++(www.cppentry.com),sizeof
可以作用在对象以及类别上。但是不能够做以下的事:struct SomeType { OtherType member; };
sizeof(SomeType::member); // 直接由SomeType型別取得非靜態成員的大小,C++(www.cppentry.com)03不行。 C++(www.cppentry.com)11允許
这会传回OtherType
的大小。C++(www.cppentry.com)03并不允许这样做,所以会引发编译错误。C++(www.cppentry.com)11将会允许这种使用。
垃圾回收机制
是否会自动回收那些无法被使用到 (unreachable) 的动态分配对象由实现决定。