设为首页 加入收藏

TOP

C++11 C++标准程序库的变更
2011-12-12 12:43:07 来源: 作者: 【 】 浏览:1308
Tags:标准 程序 变更

C++(www.cppentry.com)11 标准程序库有数个新机能。其中许多可以在现行标准下实现,而另外一些则依赖于(或多或少)新的 C++(www.cppentry.com)11 核心语言机能。

新的程序库的大部分被定义于C++(www.cppentry.com)标准委员会的Library Technical Report

 (称TR1),于2005年发布。各式 TR1 的完全或部分实现目前提供在命名空间 std::tr1
。C++(www.cppentry.com)11 会将其移置于命名空间 std 之下。


标准库组件上的升级

目前的标准库能受益于 C++(www.cppentry.com)11 新增的一些语言特性。举例来说,对于大部份的标准库容器而言,像是搬移内含大量元素的容器,或是容器之内对元素的搬移,基于右值引用 (Rvalue reference) 的 move

 建构子都能优化前述动作。在适当的情况下,标准库组件将可利用 C++(www.cppentry.com)11 的语言特性进行升级。这些语言特性包含但不局限以下所列:
  • 右值引用和其相关的 move
  •  支持
  • 支持 UTF-16 编码,和 UTF-32 字符集
  • 变长实参模板 (与右值引用搭配可以达成完美转送 (perfect forwarding))
  • 编译期常数表达式
  • Decltype
  • 显式类别转换子
  • 使用或禁用对象的默认函数
  • 此外,自 C++(www.cppentry.com) 标准化之后已经过许多年。现有许多代码利用到了标准库; 这同时揭露了部份的标准库可以做些改良。其中之一是标准库的存储器配置器 (allocator)。C++(www.cppentry.com)11将会加入一个基于作用域模型的存储器配置器来支持现有的模型。


    线程支持

    虽然 C++(www.cppentry.com)11 会在语言的定义上提供一个存储器模型以支持线程,但线程的使用主要将以 C++(www.cppentry.com)11 标准库的方式体现。

    C++(www.cppentry.com)11 标准库会提供类型 thread

     (std::thread)。若要运行一个线程,可以创建一个类型 thread 的实体,其初始实参为一个函数对象,以及该函数对象所需要的实参。通过成员函数std::thread::join() 对线程会合的支持,一个线程可以暂停直到其它线程运行完毕。若有底层平台支持,成员函数 std::thread::native_handle() 将可提供对原生线程对象运行平台特定的操作。

    对于线程间的同步,标准库将会提供适当的互斥锁 (像是 std::mutex

    ,std::recursive_mutex 等等) 和条件变量 (std::condition_variable 和std::condition_variable_any)。前述同步机制将会以 RAII 锁 (std::lock_guard 和 std::unique_lock) 和锁相关算法的方式体现,以方便程序员使用。

    对于要求高性能,或是极底层的工作,有时或甚至是必须的,我们希望线程间的通信能避免互斥锁使用上的开销。以原子操作来访问存储器可以达成此目的。针对不同情况,我们可以通过显性的存储器屏障改变该访问存储器动作的可见性。

    对于线程间异步的传输,C++(www.cppentry.com)11 标准库加入了 以及 std::packaged_task

     用来包装一个会传回异步结果的函数调用。 因为缺少结合数个 future 的功能,和无法判定一组 promise 集合中的某一个 promise 是否完成,futures 此一提案因此而受到了批评。

    更高级的线程支持,如线程池,已经决定留待在未来的 Technical Report 加入此类支持。更高级的线程支持不会是 C++(www.cppentry.com)11 的一部份,但设想是其最终实现将创建在目前已有的线程支持之上。

    std::async

     提供了一个简便方法以用来运行线程,并将线程绑定在 std::future。用户可以选择一个工作是要多个线程上异步的运行,或是在一个线程上运行并等待其所需要的数据。默认的情况,实现可以根据底层硬件选择前面两个选项的其中之一。另外在较简单的使用情形下,实现也可以利用线程池提供支持。

    多元组类别

    多元组是一个内由数个异质对象以特定顺序排列而成的数据结构。多元组可被视为是 struct

     其数据成员的一般化。

    由 TR1 演进而来的 C++(www.cppentry.com)11 多元组类别将受益于 C++(www.cppentry.com)11 某些特色像是变长参数模板。TR1 版本的多元组类别对所能容纳的对象个数会因实现而有所限制,且实现上需要用到大量的宏技巧。相反的,C++(www.cppentry.com)11 版本的多元组型基本上于对其能容纳的对象个数没有限制。然而,编译器对于模板实体化的递归深度上的限制仍旧影响了元组类别所能容纳的对象个数 (这是无法避免的情况); C++(www.cppentry.com)11 版本的多元组型不会把这个值让用户知道。

    使用变长参数模板,多元组类别的声明可以长得像下面这样:

    template <class ...Types> class tuple;

    底下是一个多元组类别的定义和使用情况:

    typedef std::tuple <int, double, long &, const char *> test_tuple; long lengthy = 12; test_tuple proof (18, 6.5, lengthy, "Ciao!"); lengthy = std::get<0>(proof); // 將 proof 的第一個元素賦值給 lengthy (索引從零開始起跳) std::get<3>(proof) = " Beautiful!"; // 修改 proof 的第四個元素

    我们可以定义一个多元组类别对象 proof

     而不指定其内容,前提是 proof 里的元素其类别定义了默认建构子 (default constructor)。此外,以一个多元组类别对象赋值给另一个多元组类别对象是可能的,但只有在以下情况: 若这两个多元组类别相同,则其内含的每一个元素其类别都要定义拷贝建构子 (copy constructor); 否则的话,赋值操作符右边的多元组其内含元素的类别必须能转换成左边的多元组其对应的元素类别,又或者赋值操作符左边的多元组其内含元素的类别必须定义适当的建构子。
    typedef std::tuple< int , double, string > tuple_1 t1; typedef std::tuple< char, short , const char * > tuple_2 t2 ('X', 2, "Hola!"); t1 = t2 ; // 可行。前兩個元素會作型別轉換, // 第三個字串元素可由 'const char *' 所建構。

    多元组类型对象的比较运算是可行的(当它们拥有同样数量的元素)。此外,C++(www.cppentry.com)11 提供两个表达式用来检查多元组类型的一些特性 (仅在编译期做此检查)。

    • std::tuple_size<T>::value
    •  回传多元组 T
     内的元素个数,
  • std::tuple_element<I, T>::type
  •  回传多元组 T 内的第 I 个元素的类别

    散列表

    在过去,不断有要求想将散列表(无序关系式容器)引进标准库。只因为时间上的限制,散列表才没有被标准库所采纳。虽然,散列表在最糟情况下(如果出现许多冲突 (collision) 的话)在性能上比不过平衡树。但实际运用上,散列表的表现则较佳。

    因为标准委员会还看不到有任何机会能将开放寻址法标准化,所以目前冲突仅能通过链地址法 (linear chaining) 的方式处理。为避免与第三方库发展的散列表发生名称上的冲突,前缀将采用 unordered 而非 hash。

    库将引进四种散列表,其中差别在于底下两个特性: 是否接受具相同键值的项目 (Equivalent keys),以及是否会将键值映射到相对应的数据 (Associated values)。

    散列表类型有无关系值接受相同键值std::unordered_set否否std::unordered_multiset否是std::unordered_map是否std::unordered_multimap是是

    上述的类型将满足对一个容器类型的要求,同时也提供访问其中元素的成员函数: insert

    , erase, begin, end。

    散列表不需要对现有核心语言做扩展(虽然散列表的实现会利用到 C++(www.cppentry.com)11 新的语言特性),只会对头文件 <functional>

     做些许扩展,并引入 <unordered_set> 和 <unordered_map>两个头文件。对于其它现有的类型不会有任何修改。同时,散列表也不会依赖其它标准库的扩展功能。

    正则表达式

    过去许多或多或少标准化的程序库被创建用来处理正则表达式。有鉴于这些算法的使用非常普遍,因此标准程序库将会包含他们,并使用各种面向对象语言的潜力。

    这个新的程序库,被定义于<regex>

    头文件,由几个新的类型所组成:
    • 正则表达式(样式)以样板类 basic_regex
    •  的实体表示
  • 样式匹配的情况以样板类 match_results
  •  的实体表示

    函数 regex_search

     是用来搜索样式; 若要搜索并取代,则要使用函数 regex_replace,该函数会回传一个新的字符串。算法regex_search 和 regex_replace 接受一个正则表达式(样式)和一个字符串,并将该样式匹配的情况存储在 struct match_results。

    底下描述了 match_results

     的使用情况:
    const char *reg_esp = "[ ,.\\t\\n;:]" ; // 分隔字元列表 std::regex rgx(reg_esp) ; // 'regex' 是樣板類 'basic_regex' 以型別為 'char' // 的參數具現化的實體 std::cmatch match ; // 'cmatch' 是樣板類 match_results' 以型別為 'const char *' // '的參數具現化的實體 const char *target = "Polytechnic University of Turin " ; // 辨別所有被分隔字元所分隔的字 if( regex_search( target, match, rgx ) ) { // 若此種字存在 const size_t n = match.size(); for( size_t a = 0 ; a < n ; a++ ) { string str( match[a].first, match[a].second ) ; cout << str << "\n" ; } }

    注意双反斜线的使用,因为 C++(www.cppentry.com) 将反斜线作为跳脱字符使用。但 C++(www.cppentry.com)11 的raw string可以用来避免此一问题。库 <regex> 不需要改动到现有的头文件,同时也不需要对现有的语言作扩展。

    通用智能指针

    这些指针是由 TR1 智能指针演变而来。注意! 智能指针是类型而非一般指针。

    shared_ptr

     是一引用计数 (reference-counted) 指针,其行为与一般 C++(www.cppentry.com) 指针即为相似。在 TR1 的实现中,缺少了一些一般指针所拥有的特色,像是别名或是指针运算。C++(www.cppentry.com)11新增前述特色。

    一个 shared_ptr

     只有在已经没有任何其它 shared_ptr 指向其原本所指向对象时,才会销毁该对象。

    一个 weak_ptr

     指向的是一个被 shared_ptr 所指向的对象。该 weak_ptr 可以用来决定该对象是否已被销毁。weak_ptr 不能被解参考; 想要访问其内部所保存的指针,只能通过shared_ptr。有两种方法可达成此目的。第一,类型 shared_ptr 有一个以 weak_ptr 为实参的建构子。第二,类型 weak_ptr 有一个名为 lock 的成员函数,其返回值为一个shared_ptr。weak_ptr 并不拥有它所指向的对象,因此不影响该对象的销毁与否。

    底下是一个 shared_ptr

     的使用样例:
    int main( ) { std::shared_ptr<double> p_first(new double) ; { std::shared_ptr<double> p_copy = p_first ; *p_copy = 21.2; } // 此時 'p_copy' 會被銷毀,但動態分配的 double 不會被銷毀。 return 0; // 此時 'p_first' 會被銷毀,動態分配的 double 也會被銷毀 (因為不再有指針指向它)。 }

    auto_ptr

     将会被 C++(www.cppentry.com) 标准所废弃,取而代之的是 unique_ptr。 unique_ptr 提供 auto_ptr 大部份特性,唯一的例外是 auto_ptr 的不安全、隐性的左值搬移。不像auto_ptr,unique_ptr 可以存放在 C++(www.cppentry.com)11 提出的那些能察觉搬移动作的容器之中。

    可扩展的随机数功能

    C 标准库允许使用rand

    函数来生成伪随机数。不过其算法则取决于各程序库开法者。 C++(www.cppentry.com) 直接从 C 继承了这部份,但是 C++(www.cppentry.com)11 将会提供产生伪乱数的新方法。

    C++(www.cppentry.com)11 的随机数功能分为两部分: 第一,一个乱数生成引擎,其中包含该生成引擎的状态,用来产生乱数。第二,一个分布,这可以用来决定产生乱数的范围,也可以决定以何种分布方式产生乱数。乱数生成对象即是由乱数生成引擎和分布所构成。

    不同于 C 标准库的 rand

    ; 针对产生乱数的机制,C++(www.cppentry.com)11 将会提供三种算法,每一种算法都有其强项和弱项:样板类整数/浮点数品质
    速度状态数*linear_congruential整数低中等[来源请求]1subtract_with_carry两者皆可中等快25mersenne_twister整数佳快624

    C++(www.cppentry.com)11 将会提供一些标准分布: uniform_int_distribution (离散型均匀分布),bernoulli_distribution (伯努利分布),geometric_distribution (几何分布), poisson_distribution (卜瓦松分布),binomial_distribution (二项分布),uniform_real_distribution (离散型均匀分布), exponential_distribution (指数分布),normal_distribution (正态分布) 和 gamma_distribution (伽玛分布)。

    底下描述一个乱数生成对象如何由乱数生成引擎和分布构成:

    std::uniform_int_distribution<int> distribution(0, 99); // 以離散型均勻分佈方式產生 int 亂數,範圍落在 0 到 99 之間 std::mt19937 engine; // 建立亂數生成引擎 auto generator = std::bind(distribution, engine); // 利用 bind 將亂數生成引擎和分布組合成一個亂數生成物件 int random = generator(); // 產生亂數

    包装引用

    我们可以通过实体化样板类 reference_wrapper

     得到一个包装引用 (wrapper reference)。包装引用类似于一般的引用。对于任意对象,我们可以通过模板类 ref 得到一个包装引用 (至于 constant reference 则可通过 cref 得到)。

    当样板函数需要形参的引用而非其拷贝,这时包装引用就能派上用场:

    // 此函數將得到形參 'r' 的引用並對 r 加一 void f (int &r) { r++; } // 樣板函式 template<class F, class P> void g (F f, P t) { f(t); } int main() { int i = 0 ; g (f, i) ; // 實體化 'g<void (int &r), int>' // 'i' 不會被修改 std::cout << i << std::endl; // 輸出 0 g (f, std::ref(i)); // 實體化 'g<void(int &r),reference_wrapper<int>>' // 'i' 會被修改 std::cout << i << std::endl; // 輸出 1 }

    这项功能将加入头文件 <utility>

     之中,而非通过扩展语言来得到这项功能。

    多态函数对象包装器

    针对函数对象的多态包装器(又称多态函数对象包装器)在语义和语法上和函数指针相似,但不像函数指针那么狭隘。只要能被调用,且其实参能与包装器兼容的都能以多态函数对象包装器称之(函数指针,成员函数指针或仿函数)。

    通过以下例子,我们可以了解多态函数对象包装器的特性:

    std::function<int (int, int)> func; // 利用樣板類 'function' // 建立包裝器 std::plus<int> add; // 'plus' 被宣告為 'template<class T> T plus( T, T ) ;' // 因此 'add' 的型別是 'int add( int x, int y )' func = &add; // 可行。'add' 的型參和回返值型別與 'func' 相符 int a = func (1, 2); // 注意: 若包裝器 'func' 沒有參考到任何函式 // 會丟出 'std::bad_function_call' 例外 std::function<bool (short, short)> func2 ; if(!func2) { // 因為尚未賦值與 'func2' 任何函式,此條件式為真 bool adjacent(long x, long y); func2 = &adjacent ; // 可行。'adjacent' 的型參和回返值型別可透過型別轉換進而與 'func2' 相符 struct Test { bool operator()(short x, short y); }; Test car; func = std::ref(car); // 樣板類 'std::ref' 回傳一個 struct 'car' // 其成員函式 'operator()' 的包裝 } func = func2; // 可行。'func2' 的型參和回返值型別可透過型別轉換進而與 'func' 相符

    模板类 function

     将定义在头文件 <functional>,而不须更动到语言本身。

    用于元编程(www.cppentry.com)的类别属性

    对于那些能自行创建或修改本身或其它程序的程序,我们称之为编程(www.cppentry.com)。这种行为可以发生在编译或运行期。C++(www.cppentry.com) 标准委员会已经决定引进一组由模板实现的库,程序员可利用此一库于编译期进行元编程(www.cppentry.com)。

    底下是一个以元编程(www.cppentry.com)来计算指数的例子:

    template<int B, int N> struct Pow { // recursive call and recombination. enum{ value = B*Pow<B, N-1>::value }; }; template< int B > struct Pow<B, 0> { // ''N == 0'' condition of termination. enum{ value = 1 }; }; int quartic_of_three = Pow<3, 4>::value;

    许多算法能作用在不同的数据类别; C++(www.cppentry.com) 模板支持泛型,这使得代码能更紧凑和有用。然而,算法经常会需要目前作用的数据类别的信息。这种信息可以通过类别属性 (type traits

    ) 于模板实体化时将该信息萃取出来。

    类别属性能识别一个对象的种类和有关一个类别 (class) (或 struct) 的特征。头文件 <type_traits>

     描述了我们能识别那些特征。

    底下的例子说明了模板函数‘elaborate’是如何根据给定的数据类别,从而实体化某一特定的算法 (algorithm.do_it

    )。
    // 演算法一 template< bool B > struct Algorithm { template<class T1, class T2> int do_it (T1 &, T2 &) { /*...*/ } }; // 演算法二 template<> struct Algorithm<true> { template<class T1, class T2> int do_it (T1, T2) { /*...*/ } }; // 根據給定的型別,實體化之後的 'elaborate' 會選擇演算法一或二 template<class T1, class T2> int elaborate (T1 A, T2 B) { // 若 T1 為 int 且 T1 為 float,選用演算法二 // 其它情況選用演算法一 return Algorithm<std::is_integral<T1>::value && std::is_floating_point<T2>::value>::do_it( A, B ) ; }

    通过定义在 <type_transform>

     的类别属性,自定的类别转换是可能的 (在模板中,static_cast 和 const_cast 无法适用所有情况)。

    此种编程(www.cppentry.com)技巧能写出优美、简洁的代码; 然而除错是此种编程(www.cppentry.com)技巧的弱处: 编译期的错误消息让人不知所云,运行期的除错更是困难。

    用于计算函数对象返回类型的统一方法

    要在编译期决定一个样板仿函数的回返值类别并不容易,特别是当回返值依赖于函数的实参时。举例来说:

    struct Clear { int operator()(int); // 參數與回返值的型別相同 double operator()(double); // 參數與回返值的型別相同 }; template <class Obj> class Calculus { public: template<class Arg> Arg operator()(Arg& a) const { return member(a); } private: Obj member; };

    实体化样板类 Calculus<Clear>

    ,Calculus 的仿函数其回返值总是和 Clear 的仿函数其回返值具有相同的类别。然而,若给定类型 Confused:
    struct Confused { double operator()(int); // 參數與回返值的型別不相同 int operator()(double); // 參數與回返值的型別不相同 };

    企图实体化样板类 Calculus<Confused> 将导致 Calculus 的仿函数其回返值和类型 Confused 的仿函数其回返值有不同的类别。对于 int

     和 double 之间的转换,编译器将给出警告。

    模板 std::result_of

     被TR1 引进且被 C++(www.cppentry.com)11 所采纳,可允许我们决定和使用一个仿函数其回返值的类别。底下,CalculusVer2 对象使用 std::result_of 对象来推导其仿函数的回返值类别:
    template< class Obj > class CalculusVer2 { public: template<class Arg> typename std::result_of<Obj(Arg)>::type operator()(Arg& a) const { return member(a); } private: Obj member; };

    如此一来,在实体化 CalculusVer2<Confused>

     其仿函数时,不会有类别转换,警告或是错误发生。

    模板 std::result_of

     在 TR1 和 C++(www.cppentry.com)11 有一点不同。TR1 的版本允许实现在特殊情况下,可以无法决定一个函数调用其回返值类别。然而,因为 C++(www.cppentry.com)11支持了decltype,实现被要求在所有情况下,皆能计算出回返值类别。
    】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
    分享到: 
    上一篇C++11 已被移除或是不包含在 C++1.. 下一篇C++11 核心语言能力的提升

    评论

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