设为首页 加入收藏

TOP

C++11 核心语言能力的提升
2011-12-12 12:41:32 来源: 作者: 【 】 浏览:1108
Tags:核心 语言 能力 提升

这些机能提供了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-8UTF-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) 的动态分配对象由实现决定。

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇C++11 C++标准程序库的变更 下一篇C++11 核心语言使用性的加强

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: