C++0x 走马观花:C++标准库的改变

来源:互联网 发布:qq群优化软件破解版 编辑:程序博客网 时间:2024/05/01 23:02

 7 C++标准库的改变
  大量新的特征被引入到C++0x的标准库里。其中许多都可以在当前标准下实现,然后有一些需要依赖新的C++0x核心语言特性。

  新标准库的绝大部分定义在C++标准委员会于2005年发布的技术报告(TR1)(C++ Standards Committee's Library Technical Report)里面。各种各样TR1的全部和部分的实现,现在可以通过名字空间std::tr1获得。对于C++0x它们将被移置于名字空间std里面。然而,由于TR1的特征已经被带入到了C++0x的标准库里面,尚未在C++0x语言初始版本体现出来的特征,也要进行升级。而且,在C++03存在但不是最初TR1规范的某些特征也可能需要被增强。

  委员会打算在C++0x标准完成之后创建第二个技术报告(被称作TR2)。在C++0x未按时完成的标准库的议题将会放入TR2中,或者更后面的技术报告里面。

  下面这些C++0x的议题目前是正在进行中的。

7.1 标准库组件的升级
  C++0x提供了许多新的语言特征,目前已存在的标准库组件可以从中受益很多。比如,大多数标准库容器都可以从基于move构建函数所支持的右值引用得到改进,包括快速地移动重量级容器,以及移动这些容器的内容到新的内存位置。随着C++0x语言特征的发展,标准库组件随之而升级。这包括,但是不限于:

    - 概念(Concepts)
    - 右值引用和相关move语法的支持
    - 支持UTF-16和UTF-32字符类型
    - 变参模板(和右值引用协作可以支持前向参数传递)
    - 编译时的常量表达式
    - decltype
    - 运算符显式地转换
    - 缺省函数的使能

  此外,C++已经被标准化了很长时间,也编写了大量使用标准库的代码,于是发现标准库的某些部分也需要进行改进。在需要被改进的许多地方中,考虑的都是标准库的分配器(allocator)。所以一个新的基于范围的分配器模型将会包含在C++0x里面,作为当前模型的补充。

7.2 线程组件
  C++0x语言提供支持线程的内存模型,而实际使用线程的主要支持来自于C++0x标准库。

  线程类持有一个函数对象在新的线程里面运行。通过对线程等待(join)的支持,可以让一个线程停止直到另外一个执行的线程结束为止。为了灵活其间,给底层本地线程对象提供了一种途径进行平台相关的操作。

  关于线程之间的同步,在库里面增加了合适的互斥量(mutex)以及条件量(condition)。这可以通过使用RAII锁和锁定算法,用起来很方便。

  对于高性能的底层的工作,有时候不需要互斥量开销的线程间通讯。这可以通过使用内存位置的原子操作以及合适的内存栅栏来达到目的。原子操作的库将会被添加,这就使得对一个操作规定最小的同步代价成为可能。

  高层次的线程组件,主要是futures以及线程池,已经被要求加入到将来的C++技术报告里面。它们不是C++0x的一部分,但它们最终的实现将会完全基于线程库的特征而建立。
7.3 元组(tuple)类型
  元组是一个由异类对象构成的有序集合。元组可以看成是结构的成员变量的更一般的形式。

  C++0x的TR1元组类型可以从比如变参模板等C++0x的特征里面受益。TR1的版本要求容纳类型的实现定义的最大数目,而且要求substantial macro trickery to implement reasonably。相反,基于C++0x版本的实现不需要显式地定义类型的最大数目。尽管编译器在模板实例化时必定有递归的最大深度,C++0x版本的元组并不会把这个值显示给使用者。

  使用变参模板,元组类的定义看起来如下所示:

    template <class ...Types> class tuple;

  定义并使用元组类型的例子如下:

    typedef tuple< int, double, long &, const char * > test_tuple ;
    long lengthy = 12 ;
    test_tuple proof( 18, 6.5, lengthy, "Ciao!" ) ;
    lengthy = get<0>(proof) ;  // Assign to 'lengthy' the value 18.
    get<3>(proof) = " Beautiful!" ;  // Modify the fourth tuple’s element.

  允许创建一个元组proof而不定义它的内容,但只有当元组的元素类型都有缺省的构建函数才成。进而言之,只要两个元组的类型是相同的并且每个元素都有相应的拷贝构建函数,就可以将其中一个元组赋值给另外一个;否则,需要右边元组的每个元素类型都可以转换为左边元组对应的元素类型,或者左边元组对应的元素类型有合适的构建函数。

    typedef tuple< int , double, string       > tuple_1 t1 ;
    typedef tuple< char, short , const char * > tuple_2 t2( 'X', 2, "Hola!" ) ;
    t1 = t2 ;  // Ok, first two elements can be converted,
               // the third one can be constructed from a 'const char *'.

  关系操作也是可以使用的(对于拥有相同数目元素的元组而言),并且提供了两个表达式来检查元组的性质(仅仅在编译期间):

    tuple_size<T>::value returns the elements’ number of the tuple T,
    tuple_element<I, T>::type returns the type of the object number I of the tuple T.

7.4 哈希表
  在C++标准库里面包括哈希表(无序关联的容器)是多次被提出的要求之一。当前标准没有采用哈希表仅仅是因为时间仓促。虽然这个解决方案在最坏情况下(存在很多冲突的时候)比平衡二叉树效率稍差,但在许多实际应用中它表现的很好。

  冲突的问题只能通过拉链的方式来解决,委员会不考虑将开地址法的解决方案标准化,因为这种方法引入了大量的问题(最重要的原因是存在删除元素的情况)。由于非标准库开发了自己的哈希表的实现,为了避免和其名字冲突,就使用了一个"unordered"的前缀而不是"hash"。

  新的工具包括四种类型的哈希表,区别在于是否接受相同的关键字(唯一的关键字或者相等的关键字),以及是否将每个关键字都映射到关联的值。

    Type of hash table      Arbitrary mapped type       Equivalent keys
    ------------------      ---------------------       ---------------
    unordered_set
    unordered_multiset                                  YES
    unordered_map           YES
    unordered_multimap      YES                         YES

    新的类实现容器类的所有要求,并且拥有所有必要的方法去操作元素:insert, erase, begin, end等等。

    新的工具不需要任何C++核心语言的扩展(虽然实现的时候会利用到各种C++0x语言特征的长处),只对头文件<functional>进行了很小的扩展,以及引入了新的头文件<unordered_set>和<unordered_map>。不需要对已存在的标准类进行改变,而且也不依赖于标准库的其他扩展。

7.5 规则表达式
  许多标准化的库或多或少都提供了规则表达式。既然使用的算法是非常通用的,那么利用面向对象的语言潜力,标准库也把它给包含了进来。

  新的库定义在新的头文件<regex>里面,由下面几个新的类构成:

  - 规则表达式由模板类basic_regex的实例来表示
  - 匹配结果由模板类match_results的实例来表示

  函数regex_search用于搜索,而“搜索并替换”的函数是regex_replace,它返回一个新的字符串。 regex_search和regex_replace算法使用一个规则表达式以及一个字符串作为参数,而搜索的结果被写入到结构match_results里面。

  下面是使用match_results的示例:

    const char *reg_esp = "[ ,.//t//n;:]" ;  // List of separator characters.
    
    regex rgx(reg_esp) ;  // 'regex' is an instance of the template class
                          // 'basic_regex' with argument of type 'char'.
    cmatch match ;  // 'cmatch' is an instance of the template class
                    // 'match_results' with argument of type 'const char *'.
    const char *target = "Polytechnic University of Turin " ;
    
    // Identifies all words of 'target' separated by characters of 'reg_esp'.
    if( regex_search( target, match, rgx ) )
    {
      // If words separated by specified characters are present.
    
      for( int a = 0 ; a < match.size() ; a++ )
      {
        string str( match[a].first, match[a].second ) ;
        cout << str << "/n" ;
      }
    }

  注意,代码里面使用了两个反斜线,这是因为C++预处理器使用反斜线作为转义字符。C++0x的raw string的新的特征可以用来避免这个问题。

  库regex不需要对已存在的头文件进行变更(尽管它会根据需要使用它们),也不需要核心语言的扩展。

7.6 通用的智能指针
  这些指针来自于TR1的智能指针。

  共享指针shared_ptr是引用计数指针,很有可能成为C++的标准指针。TR1的实现里面还缺少特定的指针特征比如别名和指针运算,不过C++0x将会把这些东西加上。

  只有当没有共享指针引用开始为共享指针所创建的对象的时候,它就会自动销毁自己的内容。

  weak_ptr是对shared_ptr所引用对象的引用,它可以检查到这个对象是否已经被删除了。weak_ptr本身不能被解引用(dereference),要获得实际的指针需要创建一个shared_ptr对象。有两种途径可以实现这个能力:shared_ptr类的构建函数将weak_ptr作为参数,或者weak_ptr类有一个lock成员函数,它返回shared_ptr;weak_ptr并不拥有它指向的对象,所以它的存在不会妨碍对象的删除。

  下面是一个共享指针用法的示例:

    int main( )
    {
      shared_ptr<double> p_first(new double) ;
    
      {
        shared_ptr<double> p_copy = p_first ;
    
        *p_copy = 21.2;
    
      }  // Destruction of 'p_copy' but not of the allocated double.
    
      return 0;  // Destruction of 'p_first' and accordingly of the allocated double.
    }

  auto_ptr将会被废除,由unique_ptr来代替。它提供auto_ptr指针的所有特征,除了不安全的从左值的隐式移动。和auto_ptr指针不太一样,unique_ptr可以与C++0x的move相关容器配合使用。

7.7 可扩展的随机数技术
  C标准库提供了通过函数rand()来产生伪随机数的能力。不过,算法却完全交给库的提供商。C++继承了这个功能而没有改变它,然而C++0x提供了一种新的方法来产生伪随机数。

  C++0x的伪随机数功能可以分为两部分:一个产生器引擎,它包含了随机数产生器的状态并且可以产生伪随机数;还有个发布器,它可以确定要输出的范围和数学分布。这两个组合在一起就形成了随机数产生器对象。

  和C语言的标准的rand()不一样,C++0x技术带来了三种产生器引擎算法,各自有其优缺点。

    template class         int/float   quality     speed     size of state*
    --------------         ---------   -------     -----     --------------
    linear_congruential    int         low         medium    1
    subtract_with_carry    both        medium      fast      25
    mersenne_twister       int         good        fast      624

  C++0x还提供多个标准发布器:uniform_int, bernoulli_distribution, geometric_distribution, poisson_distribution, binomial_distribution, uniform_real, exponential_distribution, normal_distribution, 以及gamma_distribution.

  产生器和发布器组合使用如下例所示:

    std::uniform_int<int> distribution( 0, 99 ) ;
    std::mt19937 engine ;
    std::variate_generator<mt19937, uniform_int<int>> generator( engine, distribution );
    int random = generator() ;  // Assign a value among 0 and 99.

  被std::variate_generator使用的接口已被定义,使用者只需创建产生器引擎和发布器对象就可以使用这个类了。

7.8 封装为引用
    引用的封装来自于模板类reference_wrapper的实例。它和C++语言的正常的引用运算符'&'是类似的。为了获得对任何对象的引用的封装,可以使用模板类ref(对于常量引用可以使用cref)。

    当我们需要获得参数的引用而不是拷贝的时候,引用的封装对于模板函数来说就非常有用。

    // This function will obtain a reference to the parameter 'r' and increase it.
    void f( int &r )  { r++ ; }
    
    // Template function.
    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>' is instantiated
                   // then 'i' will not be modified.
      cout << i << endl ;  // Output -> 0
    
      g( f, ref(i) ) ;  // 'g<void(int &r),reference_wrapper<int>>' is instanced
                        // then 'i' will be modified.
      cout << i << endl ;  // Output -> 1
    }

    这个新的东西将会加到已存在的头文件<utility>里面,而不需要C++语言进一步的扩展。

7.9 函数对象的多态封装
  函数对象的多态封装(也被称为多态函数对象封装)在语法和语义上都与函数指针很类似,不过没有那么牢固的绑定,还可以无差别地指向任何可以被调用的东西(函数指针,成员函数指针,或者仿函),只要它的参数与相应封装的参数兼容。

  通过下面的例子可以理解它的特点:

    function<int ( int, int )> pF ;  // Wrapper creation using
                                     // template class 'function'.
    
    plus<int> add ;  // 'plus' is declared as 'template<class T> T plus( T, T ) ;'
                     // then 'add' is type 'int add( int x, int y )'.
    
    pF = &add ;  // Assignment is correct because
                 // parameters and type of return correspond.
    
    int a = pF( 1, 2 ) ;  // NOTE: if the wrapper 'pF' is not referred to any function
                          // the exception 'bad_function_call' is thrown.
    
    function<bool ( short, short )> pg ;
    if( pg == NULL )  // It’s always verified because 'pg'
                      // is not assigned to any function yet.
    {
      bool adjacent( long x, long y ) ;
      pg = &adjacent ;  // Parameters and value of return are compatible,
                        // the assignment is correct.
      struct test
      {
        bool operator()( short x, short y ) ;
      } car ;
      pg = ref(car) ;  // 'ref' is a template function that return the wrapper
                       // of member function 'operator()' of struct 'car'.
    }
    pF = pg ;  // It is correct because parameters and value of return of
               // wrapper 'pg' are compatible with those of wrapper 'pF'.

  模板类函数被定义于头文件<functional>中,不需要对C++语言进行任何改变。

7.10 元编程(meta programming)的类型萃取
  元编程可以创建一个程序,而这个程序可以创建或修改其他程序(或者它自己)。在编译阶段或者执行阶段这种情况可能发生。C++标准委员会已经决定引入一个库,允许编译期间通过模板的元编程。

  下面是一个使用现在标准通过元编程来展示什么是可能的例子:通过模板实例递归来计算幂,

    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++模板则支持泛型编程从而使代码更加紧凑而且有用。然而,对于算法来说,常常需要正使用的数据类型的信息,这些信息可以在模板类实例化的时候通过类型萃取(type traits)提取出来。

  类型萃取可以识别对象的类别,以及类(或者结构)的全部性质。它们定义在新的头文件<type_traits>里面。

  下面这个例子里面,依赖于给定数据类型的模板函数'elaborate',把两个推荐算法之一进行了实例化:

    // First way of operating.
    template< bool B > struct algorithm
    {
      template< class T1, class T2 > int do_it( T1 &, T2 & )  { /*...*/ }
    } ;
    // Second way of operating.
    template<> struct algorithm<true>
    {
      template< class T1, class T2 > int do_it()( T1 *, T2 * )  { /*...*/ }
    } ;
    
    // Instantiating 'elaborate' will automatically instantiate the correct way to operate.
    template< class T1, class T2 > int elaborate( T1 A, T2 B )
    {
      // Use the second way only if 'T1' is an integer and if 'T2' is
      // in floating point, otherwise use the first way.
      return algorithm< is_integral<T1>::value && is_floating_point<T2>::value >::do_it( A, B ) ;
    }

  通过定义在头文件<type_transform>里面的类型萃取,也可以创建类型转换运算(在模板里面static_cast和const_cast还是不够的)。

  这种方式的编程产生优雅而又简洁的代码,不过这种技术的缺点是调试:编译时让人不爽,而在程序执行时又很困难。

7.11 获得函数对象(Function Object)返回类型的统一方法
  编译时检测模板函数对象的返回类型并不直观,特别是返回值依赖于函数参数的情况。示例如下:

    struct clear
    {
      int    operator()( int    ) ;  // The parameter type is
      double operator()( double ) ;  // equal to the return type.
    } ;
    
    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    ) ;  // The parameter type is NOT
      int    operator()( double ) ;  // equal to the return type.
    } ;

  试图去实例化calculus<confused>将会造成calculus的返回类型和类confused不一致。编译器就会产生从int到double(或者反过来)类型转换的警告。

  TR1引入,并且C++0x采纳了模板类std::result_of,它可以检测和使用各种声明的函数对象的返回类型。下面这个对象calculus_ver2使用std::result_of推导出函数对象的返回类型:

    template< class Obj >
    class calculus_ver2
    {
      public:
        template< class Arg >
        typename std::result_of<Obj(Arg)>::type operator()( Arg& a ) const
        {
          return member(a) ;
        }
      private:
        Obj member ;
    } ;

  这种情况下,在函数对象calculus_ver2<confused>的实例里,就没有转换,警告和错误。

  TR1版本的std::result_of唯一的变化,是TR1版本允许确定函数调用的结果类型时失败。由于C++的改变开始支持decltype,C++0x版本的std::result_of不再需要这些特殊的情况,在所有情况下,类型都要求被计算出来。

原创粉丝点击