C++11学习笔记5——提高类型安全

1、强类型枚举

         C++98中,枚举enum是非强作用域类型,enum的成员名字都是全局可见的。另外,由于C中枚举被设计为常量数值的“别名”的本性,枚举的成员总是可以被隐式地转换为整型。很多时候,这也是不安全的。

         C++11中引入了强类型枚举(strong-typed enum),也就是将枚举封装成“枚举类”。

声明强类型枚举的方法:在enum后加上关键字 class或者struct,比如:

        enum class Type { General, Light, Medium, Heavy };

强类型枚举的优势:

        ** 强作用域,强类型枚举成员的名字不会被输出到其父作用域空间;

        ** 转换限制,强类型枚举成员的值不可以与整型隐式地相互转换;

        **可以指定底层类型,强类型枚举默认的底层类型为int,但也可以显式地指定底层类型,具体方法为在枚举名称后面加上“:type”,其中type可以是除wchar_t以外的任何整型。例如:enum class Type: char { General, Light, Medium, Heavy };,就指定了Type是基于char类型的强类型枚举。

#include <iostream>
using namespace std;

enum class Type { General, Light, Medium, Heavy };
enum class Category { General = 1, Pistol, MachineGun, Cannon };

int main()
{
        Type t = Type::Light;
        t = General; //编译失败,必须使用强类型名称
       if( t == Category::General ) //编译失败,必须使用Type中的General
          cout<<"General Weapon"<<endl;
       if( t > Type::General )   //通过编译
            cout<<"Not General Weapon"<<endl;
      if( t > 0 )  //编译失败,无法转换为int类型
           cout<<"Not General Weapon" <<endl;
      if( (int)t > 0 ) //通过编译
           cout<<"Not General Weapon"<<endl;
      cout<<is_pod<Type>::value <<endl;   // 1
      cout<<is_pod<Category>::value <<endl;  //1
      return 0;
}
注:强类型枚举没有任何class封装枚举的缺点,且都是POD类型,不会像class封装版本一样被编译器视为结构体,书写也很方便,几乎没有任何额外的开销。

        此外,C++11还对原有的枚举进行了扩展,首先,底层基本类型方面,新标准中原有枚举类型的底层类型在默认情况下,仍然由编译器来具体指定实现,但也可以像强类型枚举类一样,显式地由程序员来指定。例如 enum Type :char { General, Light, Medium, Heavy };

       另外一个扩展是作用域,新标准中枚举成员的名字除了会自动输出到父作用域,也可以在枚举类型定义的作用域内有效。例如

enum Type { General, Light, Medium, Heavy };
Type t1 = General;
Type t3 = Type::General;


2、堆内存管理:智能指针与垃圾回收

       在C++11中,auto_ptr被废弃了,取而代之的是unique_ptr、shared_ptr和 weak_ptr等智能指针。

       (1)、unique_ptr

       unique_ptr与所指对象的内存绑定紧密,不能与其他unique_ptr类型的指针对象共享所指对象的内存,每个unique_ptr都是唯一地“拥有”所指向的对象内存。

从实现上讲,unique_ptr是一个删除了拷贝构造函数,保留了移动构造函数的指针封装类型,程序员仅可以使用右值对unique_ptr对象进行构造,而且一旦构造成功,右值对象中的指针即被“窃取”,因此该右值对象即刻失去了对指针的“所有权”。

       (2)、shared_ptr

         shared_ptr允许多个该智能指针共享地“拥有”同一堆分配对象的内存。在实现上采用了引用计数,所以一旦一个shared_ptr指针放弃了“所有权”,其他的shared_ptr对对象内存的引用并不会受到影响。只有在引用计数归零时,shared_ptr才会真正释放所占用的堆内存空间。

       (3)、weak_ptr

          weak_ptr可以指向shared_ptr指针指向的对象内存,却并不拥有该内存,而使用weak_ptr成员lock,则可返回其指向内存的一个shared_ptr对象,且在所指对象内存已经无效时,返回指针空值,这在验证share_ptr指针的有效性上会很有作用。

        垃圾回收的分类,主要有两大类

        (1)、基于引用计数(reference counting garbage collector )的垃圾回收器

         引用计数主要是使用系统记录对象被引用的次数,当对象被引用的次数变为0时,该对象即被视为“垃圾”而回收。

          优点是,实现很简单,与其他垃圾回收算法相比,该方法不会造成程序暂停,因为计数的增减与对象的使用时紧密结合的。此外,引用计数也不会对系统地缓存或者交换空间造成冲击,因此被认为“副作用”较小。

          缺点是,比较难处理“环形引用”问题,此外由于计数带来的额外开销也并不小,所以在实用上也有一定的限制。

         (2)、基于跟踪处理(tracing garbage collector )的垃圾回收器

         其基本方法是产生跟踪对象的关系图,然后进行垃圾回收,使用跟踪方式的垃圾回收算法主要有以下几种:

         <1>、标记-清除(Mark-Sweep)

         该算法分两个过程,首先该算法将程序中正在使用的 对象视为“根对象”,从根对象开始查找它们所引用的队空间,并在这些堆空间上做标记。当标记结束后,所有被标记的对象就是可达对象(Reachable Object)或活对象(Live Object),而没有被标记的对象就被认为是垃圾,然后清除(Sweep)阶段会被回收掉。

         特点,活的对象不会被移动,但是其存在会出现大量的内存碎片的问题。

         <2>、标记-整理(Mark-Compact)

         该算法第一步与上述算法相同,但是第二步,在标记完之后,不再遍历所有对象清扫垃圾,而是将活的对象向“左”靠齐,这样可以解决内存碎片问题。

         特点,移动活的对象,因此相应地,程序中所有对堆内存的引用都必须更新。

        <3>、标记-拷贝(Mark-Copy )

         该算法分两部分:From和To,刚开始系统只从From的堆空间里面分配内存,当From分配满的时候系统就开始垃圾回收:从From堆空间找出所有的活的对象,拷贝到To的堆空间里。这样一来,From的堆空间里面就全剩垃圾了,而对象被拷贝到To里之后,在To里是紧凑排列的,接下来是需要将From和To交换一下角色,接着从新的From里面开始分配。

         特点,堆的利用率只有一半,而且也需要移动活的对象,从某种意义上看,这种算法其实是标记-整理算法的另一种实现而已。

        

c++11与最小垃圾回收支持

        指针的灵活使用可能是C/C++中的一大优势,而对于垃圾回收来说,却会带来很大的困扰。被隐藏的指针会导致编译器在分析指针的可达性(生命周期)时出错。

C++11为了做到最小的垃圾回收支持,首先对“安全”的指针进行了定义,也就是术语中的安全派生(safely derived)的指针。安全派生的指针是指向由new分配的对象或其子对象的指针。安全派生的指针的操作包括:

        ** 在解引用基础上的引用,比如: &*p。

        **定义明确的指针操作,比如: p + 1;

        ** 定义明确的指针转换,比如:static_cast< void* >( p )。

        ** 指针和整型之间的reinterpret_case,比如:reinterpret_cast< intptr_t>(p) 。

       在C++11中,最小垃圾回收支持是基于安全派生指针这个概念的。程序员可以通过get_pointer_safety函数来确认编译器是否支持这个特性。get_pointer_safety的原型如下:pointer_safety get_pointer_safety() noexcept

       如果返回值是pointer_safety::strict,表明编译器支持最小垃圾回收及安全派生指针等相关概念,如果是pointer_safety::relax或pointer_safety::preferred,则表明编译器并不支持。

       C++11中可以通过API通知垃圾回收不得回收某些内存。

       declare_reachable()显式地通知垃圾回收器某个对象应被认为可达的,即使它的所有指针都对回收器不可见。undeclare_reachable()则可以取消这种可达声明。

注意:

       **  通常情况下,如果我们想要程序使用垃圾回收,或者可靠的内存泄露检测,我们就必须做出必要的假设来保证垃圾回收器能工作,而为此,我们必须限制指针的使用或者使用declare_reachable/undeclare_reachable、declare_no_pointer./underclare_no_pointer来让一些不安全的指针使用免于垃圾回收器的检查。

      **  C++11对指针的垃圾回收支持仅限于系统提供的new操作符分配的内存,而malloc分配的内存则会被认为总是可达的,即无论何时垃圾回收器都不予回收。

      ** 按照C++的设计,显式地delete使用与垃圾回收并不会形成冲突。

你可能感兴趣的:(C++11学习笔记5——提高类型安全)