首页 > 代码库 > [GeekBand] 探讨C++新标准之新语法——C++ 11~14

[GeekBand] 探讨C++新标准之新语法——C++ 11~14

一、 可变参数模板(Variadic Templates)

在C++11中,出现了参数数目可变的模板,这部分在之前C++高级编程的时候就有学习到。

其实,在C中就有类似的设定。最常用的printf(),就是一个采用了一个…类型的可变参数。…类型的参数代表是一个参数组。

  1. int???sumi(int???c,???...)
    ??{
    ????va_list???ap;
  2. ????va_start(ap,??c);
  3. ????int???i;
  4. ????int???sum???=???c;
  5. ????c???=???va_arg(ap,??int);
  6. ????while??(0??!=??c)
    ??????{
  7. ????????sum???=???sum??+??c;
  8. ????????c???=???va_arg(ap,??int);
  9. ????}
  10. ????return???sum;
    ??}

    在C中的…参数可以用va_list,va_start,va_arg等宏定义进行操作,其中va_arg()代表取值并进行指针的偏移;这种方式要求参数的类型必须能够得知。对于上例,即给定了参数就是int型;对于printf,则是通过一个字符串参数确定了有多少个变量。

    那么在C++中的…称为Pack(包);正如之前在高级编程时介绍的一样,通常采用递归的方式进行参数调用。

  11. void?print()?{}??
  12. template?<?typename?T,?typename…?Types?>?void?print(const?T?&?firstArg,??
  13. ????const?Types?&?…args)?{??
  14. ????cout?<<?firstArg?<<?endll;??
  15. ????print(args…);??
  16. }??

    在这里和之前版本的Package不同,非常神奇的一点是,我们不再需要使用偏移量。也就是说,我们在使用的时候根本不需要知道Pack里边的参数类型是什么。

    在C++11中提出了一个新的数据类型tuple(元祖),就是利用了新的…实现的:

  17. template?<?typename?Head,?typename...Tail?>?class?tuple?<?Head,?Tail...?>?:?private?tuple?<?Tail...?>?{??
  18. ????typedef?tuple?<?Tail...?>?inherited;??
  19. ????public:?
  20. tuple()?{}??
  21. ????tuple(Head,?Tail...vtail):?m_head(v),?inherited(vtail...)?{}??
  22. ????typename?Head::type?head()?{??
  23. ????????return?m_head;??
  24. ????}??
  25. ????inherited?&?tail()?{??
  26. ????????return?*this;??
  27. ????}??
  28. ????protected:?
  29. Head?m_head;??
  30. };??

    这个用法又非常的巧妙,Tuple继承自比自己少了Head参数的Tuple,如下图中的类图:

    技术分享

    二、 两个新的关键字nullptr & auto

    1. nullptr

    nullptr对象是一个空的指针,类型是nullptr_t;在C++11之前,一直是使用0(NULL就是0的一种宏定义)来代表空指针。在C++11中引入了nullptr,这一方面提高了代码可读性,另一方面使fun(int)和fun(void*)这种重载成为可能;

    2. auto

    自动推导返回值类型,编译器本身是有类型检查的功能的。C++11的auto就是在类型检查的时候才决定到底是什么类型,而不是像之前的编译器,检查左右是否一致。

    建议是指使用在类型特别长或者特别难写的情况,否则会降低可读性。

    还有一种情况是lambda表达式经常使用auto关键字

  31. auto??I??=???[](int??x)->??bool??{??/*…*/?}????

    三、 初始化列表

    1.通用初始化方法

    在之前,初始化的时候如果调用构造函数,则要使用小括号;如果是创建对象数组,则要使用大括号。而在C++11中,我们可以使用大括号进行所有的初始化操作,包括诸如int values[] {1,2,3}及complex<double> c{4,3} [等价于c(4,3)].其内部时进行了一个Initializer List的转化,关于initializer list的信息见下一节。

    必须提出的是,作为数组进行初始化时,大括号中的参数是一个一个传给变量进行初始化的,并不能提供多个的初始化。

    2.std::initializer_list<>

  32. #include?<?iostream?>?
  33. #include?<?stdio.h?>?
  34. #include?<?algorithm?>?
  35. #include?<?functional?>?
  36. using?namespace?std;??
  37. class?Print:?public?binary_function?<??
  38. ????const?char?*?,?int,?void?>?{??
  39. ????????public:?void?operator()(const?char?*?p,?int?a)?const?{??
  40. ????????????cout?<<?a?<<?‘?‘;??
  41. ????????}??
  42. ????};??
  43. class?P?{??
  44. ????public:?P(int?a,?int?b)?{??
  45. ????????cout?<<?"P(int,int),a="?<<?a?<<?",b="?<<?b?<<?endl;??
  46. ????}??
  47. ????P(initializer_list?<?int?>?initlist)?{??
  48. ????????cout?<<?"P(initializer_list<int>),values=?";??
  49. ????????for_each(initlist.begin(),?initlist.end(),?bind1st(Print(),?"%d?"));??
  50. ????????cout?<<?endl;??
  51. ????}??
  52. };??
  53. int?main()?{??
  54. ????P?p?{??
  55. ????????77,?5??
  56. ????};??
  57. ????P?q(77,?5);??
  58. }??

    创建p时会适配到初始化列表为参数;而创建q时则会适配到以两个int为参数。注意即使没有initializer_list版本的构造器,编译一样可以通过,因为发生了initializer_list的自动类型转换。

    3.initializer_list源码分析

    template<class _E>

    class initializer_list

    {

    const _E* __array;

    size_t __len;

    // The compiler can call a private constructor.

    initializer_list(const _E* __a, size_t __l)

    : __array(__a), __len(__l) { }

    public:

    initializer_list()

    : __array(NULL), __len(0) { }

    // Number of elements.

    size_t size() const

    { return __len; }

    // First element.

    const _E* begin() const

    { return __array; }

    // One past the last element.

    const _E* end() const

    { return begin() + size(); }

    };

    }

    Initializer内部使用array数据结构的迭代器(指针),但他并不内含一个array,可以视作是一个浅拷贝。在C++11的标准库中,所有的容器都添加了使用initializer_list的版本。

    诸如max()之类的函数也添加了接受Initializer_list的版本。以前,max只能进行两个值的比较;而现在,可以采用类似于max({1,2,3,4,5})之类的表达方式进行任意参数的比较。

    4. 由于初始化列表带来的explicit关键字用法的变化

    在C++11之前,explicit关键字只能用于构造函数具有一个实参,有这样的情况:

  59. class??Complex??{????????
  60. ????int??real,??imag;????????
  61. ????explicit??Complex(int??re,??int??im??=??0):??real(re),??imag(im)??{}????????
  62. ????Complex??operator??+??(const??Complex??&??x)??{????????????
  63. ????????return??Complex((real??+??x.real),???(imag??+??x.imag));????????
  64. ????}????
  65. };????
  66. int??main()??{????????
  67. ????Complex??a(0,??1);????????
  68. ????Complex??b??=??a??+??1;??//error,explicit????
  69. }????

    四、 新的for语法

  70. for?(decl:?coll)?{??
  71. ????Statement;??
  72. }??

    类似于python的for语法,decl指向coll容器的每一个元素,直到容器尾。如果你想对容器中的元素进行修改,可以采用传引用的方法,如下例:

  73. for?(auto?&?elem:?vec)?{??
  74. ????elem?+=?3;??
  75. }??

    也可以是向量组中的每一个元素都能作为单参构造的参数,其实就是通过构造函数实现的自动类型转换。

    五、保留默认函数的方法

    一般情况下,当我们创建了构造函数,拷贝构造函数,赋值操作的情况时候,默认的函数就会自动消失。现在,C++11允许我们使用=default和=delete两个关键字声明来保留或删除原有的,如下例:

  76. Zoo(const?Zoo?&?)?=?delete;?//删除已经存在的版本,可能是编译器给的或自己写的;??
  77. Zoo(const?Zoo?&?)?= default;?//采用默认定义,对于构造函数,就是保留无参构造。??

    右值引用(C++11引入)也可以使用这两个关键字,右值引用的知识在之后介绍。

    在使用这两个关键字时,其能否通过编译,关键就是看是否有二义性,或是否有先定义了再=delete的情形(在这种情形下,你先已经定义了一个函数,然后又说要删除这个函数,编译器会不知所措)

    利用=delete,可以实现不允许拷贝构造的类。在Singleton设计模式,原来是采用了一个私有的拷贝构造函数,现在我们有了新的方法。

    六、别名(Alias)(using新用法)

    1. 模板别名

    在C++11之后,using有了新的用法:

  78. template??<??typename??T??>??
  79. using??Vec??=??std::vector??<??T,??MyAlloc??<??T??>>??;????

    用宏是达不到这样的效果的 。

    在之前提及过的模板模板参数中,也涉及到了using的用法。使用模板模板参数的方法编译器无法调用向量参数的默认值,而每一个容器都有一个默认的第二参数,即内存分配器;具体写法在笔者《C++高级编程》笔记中有涉及到,不再重复讲述。

    2. 类型别名(与typedef相似)

    以下两条语句等价:

  80. typedef?void(?*?p)(int,?int);??
  81. using?func?=?void(?*?)(int,?int);??

    第二种方式(新的)提高了可读性。

    七、无异常声明noexcept

  82. void?foo()?noexcept;??

    表示foo不再抛出异常。我们知道,C++的异常处理是逐级上报的形式,那么这种情况下也就是说,如果foo()内部发生了异常,异常只在foo内部处理,如果foo处理不了,就直接中断程序(否则则会一直抛到main才退出程序)。这提高了异常处理的效率。

    需要注意的是,右值引用的移动语义构造和赋值必须有noexpect声明。

    八、关于继承的两个关键字

    1.override

  83. Struct?Base?{??
  84. ????virtual?void?vfunc(float)?{}??
  85. };??
  86. Struct?Derived1:?base?{??
  87. ????virtual?void?vfunc(int)?{}??
  88. };??

    有时候难免会发生写错的情况,如上例,我们本想复写这一虚函数,不过不小心写错了。

    在C++11中添加了一个安全检查关键字 override:

  89. Struct?Derived1:?base?{??
  90. ????virtual?void?vfunc(int) override?{}??
  91. };??

    这样如果你写下来override,则相当于告诉编译器自己要复写,编译器发现没有match会报错。

    2.final

    用于虚函数中,高速编译器,这个类或是这个虚函数不再允许派生或复写。例如:

    struct Base final{…};

[GeekBand] 探讨C++新标准之新语法——C++ 11~14