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;
在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使用与垃圾回收并不会形成冲突。