今天二刷《深入理解C++11》,就顺带把我在印象笔记的摘录传到CSND上,禁止转载!!!
全部笔记链接:
- 《深入理解C++11》笔记(第二章. 保证稳定性和兼容性)
- 《深入理解C++11》笔记(第三章. 通用为本,专用为末)
- 《深入理解C++11》笔记(第四章. 新兵易学,老兵易用)
- 《深入理解C++11》笔记(第五章. 提高类型安全)
- 《深入理解C++11》笔记(第六章. 提高性能及操作硬件的能力)
- 《深入理解C++11》笔记(第七章. 为改变思考方式而改变)
- 《深入理解C++11》笔记(第八章. 融入实际的应用)
文章目录
提高类型安全
1 强类型枚举
1.1 枚举:分门别类与数值的名字
-
宏
#define Male 0 #define Female 1
宏的弱点在于其定义的只是预处理阶段的名字,如果代码中有Male或者Female的字符串,无论在什么位置一律将被替换。这在有的时候会干扰到正常的代码。
-
匿名枚举
enum { Male, Female };
相比于宏的实现,匿名枚举不会有干扰正常代码的尴尬。
-
静态常量
const static int Male = 0; const static int Female = 1;
Male和Female的名字同样得到编译时期检查。由于是静态常量,其名字作用域也被很好地局限于文件内。不过相比于enum,静态常量不仅仅是一个编译时期的名字,编译器还可能会为Male和Female在目标代码中产生实际的数据,这会增加一点存储空间。
1.2 有缺陷的枚举类型
- 非强类型作用域:与C++中具名的namespace、class、struct及union必须通过“名字:成员名”的方式格格不入
- 允许隐式转换为整型
- 占用存储空间及符号性不确定
1.3 强类型枚举以及C++11对原有枚举类型的扩展
- 强类型枚举(枚举类)
enum class Type { General, Light, Medium, Heavy };
- 优点
- 强作用域,强类型枚举成员的名称不会被输出到其父作用域空间。
- 转换限制,强类型枚举成员的值不可以与整型隐式地相互转换。
- 可以指定底层类型。强类型枚举默认的底层类型为int,但也可以显式地指定底层类型,具体方法为在枚举名称后面加上“:type”,其中type可以是除wchar_t以外的任何整型。
enum class Type: char { General, Light, Medium, Heavy };
- 实例
#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;
}
// 编译选项:g++ -std=c++11 5-1-5.cpp
- 由于可以指定底层基于的基本类型,我们可以避免编译器不同而带来的不可移植性。此外,设置较小的基本类型也可以节省内存空间
#include <iostream>
using namespace std;
enum class C : char { C1 = 1, C2 = 2};
enum class D : unsigned int { D1 = 1, D2 = 2, Dbig = 0xFFFFFFF0U };
int main() {
cout << sizeof(C::C1) << endl; // 1
cout << (unsigned int)D::Dbig << endl; // 编译器输出一致,4294967280
cout << sizeof(D::D1) << endl; // 4
cout << sizeof(D::Dbig) << endl; // 4
return 0;
}
// 编译选项:g++ -std=c++11 5-1-6.cpp
- 相比于原来的枚举,强类型枚举更像是一个属于C++的枚举。但为了配合新的枚举类型,C++11还对原有枚举类型进行了扩展。
- 支持显示的设置底层类型
enum Type: char { General, Light, Medium, Heavy };
type可以是除wchar_t以外的任何整型
- 作用域
在C++11中,枚举成员的名字除了会自动输出到父作用域,也可以在枚举类型定义的作用域内有效。比如:
enum Type { General, Light, Medium, Heavy };
Type t1 = General;
Type t2 = Type::General;
- 支持显示的设置底层类型
- 匿名强类型枚举
enum class { General, Light, Medium, Heavy } weapon;
int main() {
weapon = General; // 无法编译通过
bool b = (weapon == weapon::General); // 无法编译通过
return 0;
}
// 编译选项:g++ -std=c++11 5-1-7.cpp
事实上,在使用enum class的时候,总应该提供一个名字。当然也可以通过decltype来获得匿名强类型枚举的类型并进行使用,但没有什么意义。
2 堆内存管理:智能指针与垃圾回收
2.1 显式内存管理
- 存在的问题
- 野指针
- 重复释放
- 内存泄漏
2.2 C++的智能指针
-
C++98的智能指针通过
auto_ptr
来实现auto_ptr(new int);
缺点:拷贝时返回一个左值,不能调用delete[]等,所以在C++11中被废弃了
-
unique_ptr
从实现上说,unique_ptr则是一个删除了拷贝构造函数、保留了移动构造函数的指针封装类型 -
shared_ptr
略 -
weak_ptr
weak_ptr的使用更为复杂一点,它可以指向shared_ptr指针指向的对象内存,却并不拥有该内存。而使用weak_ptr成员lock,则可返回其指向内存的一个shared_ptr对象,且在所指对象内存已经无效时,返回指针空值(nullptr)。这在验证share_ptr智能指针的有效性上会很有作用。
#include <memory>
#include <iostream>
using namespace std;
void Check(weak_ptr<int> & wp) {
shared_ptr<int> sp = wp.lock(); // 转换为shared_ptr<int>
if (sp != nullptr)
cout << "still " << *sp << endl;
else
cout << "pointer is invalid." << endl;
}
int main() {
shared_ptr<int> sp1(new int(22));
shared_ptr<int> sp2 = sp1;
weak_ptr<int> wp = sp1; // 指向shared_ptr<int>所指对象
cout << *sp1 << endl; // 22
cout << *sp2 << endl; // 22
Check(wp); // still 22
sp1.reset();
cout << *sp2 << endl; // 22
Check(wp); // still 22
sp2.reset();
Check(wp); // pointer is invalid
}
// 编译选项:g++ -std=c++11 5-2-2.cpp
2.3 垃圾回收的分类
- 基于引用计数
难以处理引用计数问题 - 基于跟踪处理
- 标记-清除
会出现存在大量内存碎片的问题 - 标记-整理
顾名思义,需要整理(更新程序中所有对堆内存的引用) - 标记-拷贝
堆内存利用率只有一半
- 标记-清除
2.4 C++与垃圾回收
而要保证安全的垃圾回收,首先必须知道C/C++语言中什么样的行为可能导致垃圾回收中出现“不安全”的状况。简单地说,不安全源自于C/C++语言对指针的“放纵”,即允许过分灵活的使用。
- 实例1
int main(){
int* p = new int;
p += 10; // 移动指针,可能导致垃圾回收器
p -= 10; // 回收原来指向的内存
*p = 10; // 再次使用原本相同的指针则可能无效
}
- 实例2
int main() {
int *p = new int;
int *q = (int*)(reinterpret_cast<long long>(p) ^ 2012); // q隐藏了p
// 做一些其他工作,垃圾回收器可能已经回收了p指向对象
q = (int*)(reinterpret_cast<long long>(q) ^ 2012); // 这里的q == p
*q = 10;
}
// 编译选项:g++ 5-2-4.cpp
2.5 C++11与最小垃圾回收支持
- 安全的指针(安全派生的指针)
在C++11的规则中,最小垃圾回收支持是基于安全派生指针这个概念的 - 一些函数
- 检查编译器是否支持如上特性
pointer_safety get_pointer_safety() noexcept
返回值:pointer_safety::strict、pointer_safety::relax、pointer_safety::preferred
- 如果程序员代码中出现了指针不安全使用的状况,C++11允许程序员通过一些API来通知垃圾回收器不得回收该内存。C++11的最小垃圾回收支持使用了垃圾回收的术语,即需声明该内存为“可到达”的:
void declare_reachable( void* p );
template <class T> T *undeclare_reachable(T *p) noexcept;
- 有的时候程序员会选择在一大片连续的堆内存上进行指针式操作,为了让垃圾回收器不关心该区域,也可以使用如下函数来告诉垃圾回收器该内存区域不存在有效的指针:
void declare_no_pointers(char *p, size_t n) noexcept;
void undeclare_no_pointers(char *p, size_t n) noexcept;
- 检查编译器是否支持如上特性
2.6 垃圾回收的兼容性
C++11标准中对指针的垃圾回收支持仅限于系统提供的new操作符分配的内存,而malloc分配的内存则会被认为总是可达的,即无论何时垃圾回收器都不予回收。因此使用malloc等的较老代码的堆内存还是必须由程序员自己控制。
- 注:在ubuntu 18.04 、 g++版本 7.5.0 上测试是不支持最小垃圾回收机制