CPP11新特性与应用

本文主要整理了C++11新特性相关笔记,用以学习掌握

  • 快速初始化成员变量

C++98中,对于类中的成员变量,仅对常量的静态成员(且为整型或者枚举型)才能就地初始化。C++11中,标准允许非静态成员变量的初始化有多种形式。具体而言,除了初始化列表外,在C++11中,标准还允许使用等号=或者花括号{}进行就地的非静态成员变量初始化。当就地初始化和初始化列表同时存在时,初始化列表的效果总是优先于就地初始化的。此外,值得注意的是,对于非常量的静态成员变量,C++11则与C++98保持了一致。程序员还是需要到头文件以外定义它,这会保证编译时,类静态成员的定义最后只存在于一个目标文件中

  • 非静态成员的sizeof

在C++98中,对非静态成员变量使用sizeof是不能通过编译的。在C++98中,只有静态成员,或者对象的实例才能对其成员进行sizeof操作。C++11则支持以上操作。

  • 扩展的friend语法

friend关键字用于声明类的友元,友元可以无视类中的成员属性。无论成员是public,protected或是private的,友元类或者友元函数都可以访问。这就破坏了面向对象编程中封装性的概念。因此,使用friend关键字充满了争议性。
C++11中,声明一个类为另外一个类的友元时,不在需要使用class关键字。虽然在C++11中这是一个小的改进,却会带来一点应用的变化——程序员可以为类模板声明友元了。

  • final/override控制

C++11采用了final关键字,使派生类不可覆盖它所修饰的虚函数。从接口派生的角度而言,final可以在派生过程中任意地阻止一个接口的可重载性,这就给面向对象的程序员带来了更大的控制了。在C++中重载还有一个特点,就是对于基类声明为virtual的函数,之后的重载版本都不需要再声明该重载函数为virtual。这就是使得在多重继承时,阅读代码造成干扰。信息分散、难以阅读。在C++11中为了帮助程序员写继承结构复杂的类型,引入了虚函数描述符override,如果派生类在虚函数声明时使用了override描述符,那么该函数必须重载其基类中的同名函数,否则代码将无法通过编译。

  • 模板函数的默认模板参数

C++98标准不支持函数模板的默认模板参数,在C++11中,这一限制已经解除。与类模板不同的是,在为多个默认模板参数声明指定默认值的时候,程序员必须遵照“从右往左”的规则进行指定。而这个条件对于函数模板来说并不是必须的。

  • 外部模板

在实例化模板函数时,对于参数类型一致的模板函数,编译器会实例化出多份相同的函数。那么在各自的目标文件中,会有两份一模一样的函数代码。而在链接时,链接器还需要移除重复的实例化代码。很明显,这样的工作太过冗余,会极大的增加编译器的编译和链接时间。解决这个问题的方法基本和变量共享的思路是一样的,就是使用外部模板。
extern template void fun(int);
在使用外部模板的时候,还需要注意以下问题:如果外部模板声明于某个编译单元中,那么与之对应的显示实例化必须出现于另一个编译单元或者同一个编译单元的后续代码中;外部模板声明不能用于一个静态函数(即文件域函数),但可以用于类静态成员函数。(这一点显而易见,因为静态函数没有外部链接属性,不可能在本编译单元之外出现)

  • 局部和匿名类型做模板实参

在C++98中,标准对模板实参的类型还有一些限制。具体地讲,局部的类型和匿名的类型在C++98中都不能做模板类的实参。C++11做了支持。

  • 继承构造函数

在C++11标准中继承构造函数(using Base::Base;)被设计为跟派生类中的各种类默认函数(默认构造、析构、拷贝构造等)一样,是隐式声明的。这意味着如果一个继承构造函数不被相关代码使用,编译器不会为其产生真正的函数代码,更加节省目标代码空间。如果基类的构造函数被声明为私有成员函数,或者派生者是从基类中虚继承的,那么就不能够在派生类中声明继承构造函数。一旦使用了继承构造函数,编译器就不会再为派生类生成默认构造函数了。

  • 委派构造函数

在C++11中,所谓委派构造函数,就是指委派函数将构造的任务委派给了目标构造函数来完成这样一种类构造的方式。同时,委派构造函数只能在函数体中为成员变量赋初值。因为委派构造函数不能有初始化列表。

  • 移动语义

在C++11中,这样的“偷走”临时变量中的资源的构造函数,就被称为“移动构造函数”。而这样的“偷”的行为,则称之为“移动语义”(move semantics).

avatar

  • 左值、右值与右值引用

在C++中有一个被广泛认同的说法,那就是可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值。在C++11中,右值是由两个概念构成的,一个是将亡值(xvalue, eXpiring Value),另一个则是纯右值(prvalue, Pure Rvalue)。其中纯右值就是C++98标准中右值的概念,讲的是用于辨识临时变量和一些不跟对象关联的值。而将亡值则是C++11新增的跟右值引用相关的表达式,这样表达式通常是将要被移动的对象。在C++11中,右值引用就是对一个右值进行引用的类型。事实上,由于右值通常不具有名字,我们也只能通过引用的方式找到它的存在。
在C++11中,标准库在中提供了一个有用的函数std::move,它的功能是将一个左值强制转化为右值引用,继而我们可以通过右值引用使用该值。
在C++11中,拷贝构造/赋值和移动构造/赋值函数必须同时提供,或者同时不提供,程序员才能保证类同时具有拷贝和移动语义。

  • 完美转发

所谓完美转发(perfect forwarding),是指在函数模板中,完全依照模板的参数类型,将参数传递给函数模板中调用的另外一个函数。

  • 显式转换操作符

在C++11中,标准将explicit的使用范围扩展到了自定义的类型转换操作符上,以支持所谓的“显式类型转换”。explicit关键字作用于类型转换操作符上,意味着只有在直接构造目标类型或者显式类型转换的时候可以使用该类型。

  • POD类型

Plain Old Data在C++中是非常重要的一个概念,通常用于说明一个类型的属性,尤其是用户自定义类型的属性。Old体现了其与C的兼容性,比如可以用最老的memcpy()函数进行复制,使用memset()进行初始化。C++11将POD划分为两个基本概念的合集:平凡的(trivial)和标准布局的(standard layout)
平凡的定义:

  1. 拥有平凡的默认构造函数和析构函数:平凡的默认构造函数就是说构造函数“什么都不干”,通常情况下,不定义类的构造函数,编译器就会为我们生产一个平凡的默认构造函数。而一旦定义了构造函数,即使构造函数不包含参数,函数体里也没有任何代码,那么该构造函数也不再是平凡的了。
  2. 拥有平凡的拷贝构造函数和移动构造函数。
  3. 拥有平凡的拷贝赋值运算符和移动赋值运算符。
  4. 不能包含虚函数以及虚基类。

标准布局的定义:

  1. 所有非静态成员有相同的访问权限(public, protected, private)
  2. 在类或者结构体继承时,满足以下两种情况之一:
  • 派生类中有非静态成员,且只有一个仅包含静态成员的基类

  • 基类有非静态成员,而派生类没有非静态成员

  1. 类中的第一个非静态成员类型与其基类不同(在C++标准中,如果基类没有成员,标准允许派生类的第一个成员与基类共享地址。因为派生类的地址总是“堆叠”在基类之上的,所以这样的地址共享,表明了基类并没有占据任何实际空间)
  2. 没有虚函数和虚基类
  3. 所有非静态数据成员均符合标准布局类型,其基类也符合标准布局。

使用POD有什么好处?

  1. 字节赋值,代码中可以安全地使用memset和memcpy对POD类型进行初始化和拷贝等操作
  2. 提供对C内存布局兼容。C++程序可以与C函数进行相互操作。
  3. 保证了静态初始化的安全有效。