C++11特性的学习之提高类型安全(四)

  目录括号内为适合人群,所有库作者的内容暂不做学习,可自行查阅《深入理解C++11:C++11新特性解析与应用》。网盘链接: https://pan.baidu.com/s/1Jf29R7-foOoXJ5UW3mTKVA 密码: 7vgq

目录

1.强类型枚举(部分人)
2.堆内存管理:智能指针与垃圾回收(类,库作者)
  ①显式内存管理
  ②C++11的智能指针
  ③垃圾回收的分类
  ④C++与垃圾回收
  ⑤C++11与最小垃圾回收支持
  ⑥垃圾回收的兼容性

1.强类型枚举 ^

  C++11标准中,引入了一种新的枚举类型,即“枚举类”,又称“强类型枚举”。声明强类型枚举只需要在enum后加上关键字class,如:

enum class Color { Red,Blue,Green,Yellow };

  强类型枚举具有以下几点优势:

  • 强作用域,强类型枚举成员的名称不会被输出到其父作用域空间
  • 转换限制,强类型枚举成员的值不可以与整型隐式地相互转换
  • 可以指定底层类型。强类型枚举默认的底层类型为int,但也可以显式地指定底层类型,具体方法为在枚举名称后面加上“:type”,其中type可以是除wchar_t以外的任何整型。如:
  • enum class Color :char { Red, Blue, Green, Yellow };

      普通枚举类型与强枚举类型比较的例子:

    enum Color1 { Red1, Blue1, Green1, Yellow1 };      //普通的枚举类型
    enum class Color2 { Red2, Blue2, Green2, Yellow2 };//强枚举类型
    
    int main()
    {
        //普通枚举成员可以直接调用
        cout << Red1 << " " << Blue1 << " " << Green1 << " " << Yellow1 << endl;
        //强枚举成员则需要枚举名::成员名,且不能被cout输出
        //cout<<Color::Red2<<" " << Color::Blue2 << " " << Color::Green2<< " " << Color::Yellow2 << endl;
    
        Color1 c1 = Red1;
        c1 = Blue1;
        Color2 c2 = Color2::Red2;
        //不能通过编译
        //c2 = Blue2;
        if (c1 == Red1)
            cout << "红1" << endl;
        if (c2 == Color2::Red2)
            cout << "红2" << endl;
        //普通枚举可以直接和整型进行比较
        if (c1 >= 0)
            cout << "大于等于0" << endl;
        //强枚举不能直接与整型比较,因为不会隐式转换成整型,需要显式转换   
        //if (c2 >= 0)
            //cout << "大于等于0" << endl;
        if ((int)c2 >= 0)
            cout << "大于等于0" << endl;
        cout << is_pod<Color1>::value << endl; //输出:1,是POD类型
        cout << is_pod<Color2>::value << endl; //输出:1,是POD类型
    }

      为了配合强类型枚举,C++11对原有枚举类型进行了扩展,使其也可以像强类型枚举一样,显式地指定成员类型,如:

    enum Color1 :char{ Red1, Blue1, Green1, Yellow1 };

      第二个扩展则是作用域,即:

    enum Color1 :char{ Red1, Blue1, Green1, Yellow1 };
    int main()
    {
        Color1 c1 = Red1;   //在父作用域里
        c1 = Color1::Blue1; //在枚举类型自己定义的作用域里
    }

    注:匿名的enum class,因为其实强类型作用域的,所以匿名的enum class可能啥都做不了,通常使用强类型枚举都应该为其提供一个名字。

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

      

      ①显式内存管理 ^

      我们在处理现实生活中的C/C++程序的时候,常会遇到诸如程序运行时突然退出,或占用的内存越来越多,这些问题的源头都跟C/C++中的显式堆内存管理有关。通常有以下几种情况:

  • 野指针:一些内存单元已被释放,之前指向它的指针却还在被使用,这些内存有可能被运行时系统重新分配给程序使用,从而导致了无法预知的错误。
  • 重复释放:程序试图去释放已经被释放过的内存单元,或者释放已经被重新分配过的内存单元,就会导致重复释放错误。通常重复释放内存会导致C/C++运行时系统打印出大量错误及诊断信息。
  • 内存泄露:不再需要使用的内存单元如果没有释放就会导致内存泄露。如果程序不断地重复进行这类操作,将会导致内存占用剧增。
  •   为了应对这些问题,C++中提供了智能指针,在C++11标准中,智能指针被进行了改进,以更加适应实际的应用需求。而进一步,标准库还提供了所谓“最小垃圾回收”的支持。

      ②C++11的智能指针 ^

      在C++98中,智能指针通过一个模板类型“auto_ptr”来实现,但由于有一些缺点(拷贝时返回一个左值,不能调用delete []等),在C++11标准中被废弃了,改用unique_ptr,shared_ptr和weak_ptr等智能指针来自动回收堆分配的对象。C++11中使用新的智能指针的简单例子如下:

    int main()
    {
        unique_ptr<int> p1(new int(11));
        //unique_ptr<int> p2(p1);     //不能通过编译
        cout<<*p1<<endl;              //输出:11
        unique_ptr<int> p3(move(p1)); //现在p3是数据唯一的unique_ptr智能指针
        cout<<*p3<<endl;              //输出:11
        //cout<<*p1<<endl;              //运行时错误
        p3.reset();                   //显式释放内存
        p1.reset();                   //p1已经不指向任何数据,但不会导致运行时错误
        //cout<<*p3<<endl;              //运行时错误
    
        shared_ptr<int> pp1(new int(22));
        shared_ptr<int> pp2=pp1;
    
        cout<<*pp1<<endl;             //输出:22
        cout<<*pp2<<endl;             //输出:22
    
        pp1.reset();
        cout<<*pp2<<endl;             //输出:22
    }

  • unique_ptr智能指针跟其名字一样,独一的,unique_ptr智能指针是独享数据,没有拷贝构造函数,但由移动构造函数用来“窃取”数据。
  • shared_ptr智能指针也跟其名字一样,共享的,允许多个该智能指针共享地“拥有”同一堆分配对象的内存。实现上采用了引用计数,只有在引用计数归零时,才会真正释放所占有的堆内存的空间。
  •   除了以上两种智能指针,C++11标准中还有weak_ptr。weak_ptr可以指向shared_ptr指针指向的对象内存,却并不拥有该内存。使用weak_ptr成员lock,可以返回其指向内存的一个shared_ptr对象,且在所指对象内存已经无效时,返回指针空值。这在验证shared_ptr智能指针的有效性上会有很大作用。如下:

    int main()
    {
        shared_ptr<int> pp1(new int(22));
        shared_ptr<int> pp2 = pp1;
        weak_ptr<int> ppp = pp1;
    
        cout << *pp1 << endl;             //输出:22
        cout << *pp2 << endl;             //输出:22
    
        pp1.reset();
        if (ppp.lock() == nullptr)
            cout << "指针为空1" << endl;   //shared_ptr对象还在,不输出
        cout << *pp2 << endl;             //输出:22
        pp2.reset();
        if (ppp.lock() == nullptr)
            cout << "指针为空2" << endl;   //shared_ptr对象内存被释放,输出
    }

      ③垃圾回收的分类 ^

      ”垃圾“,即我们之前使用过,现在不再使用或者没有任何指针再指向的内存空间。而将这些“垃圾”收集起来以便再次利用的机制,被称为”垃圾回收“。垃圾回收的方式很多,主要分为两大类:

      ①基于引用计数的垃圾回收器

       引用计数主要是使用系统记录对象被引用的次数。当对象被引用的次数变为0时,该对象即可被视作“垃圾”而回收。优点:该方法不会造成程序暂停,也不会对系统的缓存或者交换空间造成冲击。缺点:比较难处理“环形引用”问题,此外由于计数带来的额外开销也并不小,所以在实用上也有一定的限制。

      ②基于跟踪处理的垃圾回收器

       相比于引用计数,跟踪处理的垃圾回收机制被更广泛地应用。其基本方法是产生追踪对象的关系图,然后进行垃圾回收。使用跟踪方式的垃圾回收算法主要有以下几种:

  • 1.标记 - 清除
  • 2.标记 - 整理
  • 3.标记 - 拷贝
  •   ④C++与垃圾回收 ^

      指针的灵活使用可能是C/C++中的一大优势,而对于垃圾回收来说,却会带来很大的困扰。如下:

    int main()
    {
        int *p = new int;   
        p += 10;          //移动指针,可能导致垃圾回收器
        p -= 10;          //回收原来指向的内存
        *p = 10;          //再次使用原本相同的指针则可能无效
    }

      ⑤C++11与最小垃圾回收支持 ^

      C++11新标准为了做到最小的垃圾回收支持,首先对“安全”的指针进行了定义,即安全派生的指针。安全派生的指针是指向由new分配的对象或其子对象的指针。安全派生指针的操作包括:

    ①在解引用基础上的引用,如:&*p。
    ②定义明确的指针操作,如:p+1。
    ③定义明确的指针转换,如:static_cast<void*>(p)。
    ④指针和整型之间的reinterpret_cast,如:reinterpret_cast<intptr_t>(p)
    

    注:intptr_t是C++11中一个可选择实现的类型,其长度等于平台上指针的长度(通过decltype声明)

      在C++11的标准中,最小垃圾回收支持是基于安全派生指针这个概念的。我们可以用过get_pointer_safety函数查询来确认编译器是否支持这个特性。其返回一个pointer_safety类型的值,如果该值为pointer_safety::strict,则表明编译器支持最小垃圾回收及安全派生指针等相关概念,如果该值为pointer_safety::relax或pointer_safety::preferred,则表明编译器不支持。

      如果代码中出现了指针不安全使用的状况,C++11允许我们通过一些API来通知垃圾回收器回收该内存,即declare_reachable函数和函数模板undeclare_reachable。declare_reachable()显示地通知垃圾回收器某一个对象应被认为可到达(垃圾回收的术语),而undeclare_reachable()则是取消这种可达声明。

      ⑥垃圾回收的兼容性 ^

      尽管在设计C++11标准时想尽可能保证向后兼容,但对于垃圾回收来说,破坏向后兼容是不可避免的。想让老的代码毫不费力地使用垃圾回收,现实情况下对大多数代码是不可能的。

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

    猜你喜欢

    转载自blog.csdn.net/qq_17044529/article/details/82470264