目录括号内为适合人群,所有库作者的内容暂不做学习,可自行查阅《深入理解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分配的内存则会被认为总是可达的,即无论何时垃圾回收器都不予回收。