第十二章 异常、智能指针
异常
- 错误
- 编程过程中的常见错误类型
- 语法错误
- 逻辑错误
- 异常
- …
- 异常
- 异常是一种在程序运行过程中的发生的不好预测的错误(比如内存不够)
- 异常没有被处理,会导致程序终止
- 系统异常:
-
某些时候写代码导致系统会主动抛出异常
//系统抛出异常 cout << "1" << endl; //如果内存不够,就会抛出异常(运行过程中抛出一个错误) try { //可能会出现错误的代码放在这里 for (int i = 0; i < 9999999; i++) { int *p = new int[9999999]; } //这句代码不会执行 //try中的代码,只要某一句出现异常,后面的代码就不会执行 cout << "2" << endl; } //}catch (const std::exception&){ //catch()参数是异常类型。 catch (...) { //... 表示拦截所有的异常类型 cout << "发生了异常" << endl; } cout << "3" << endl;
-
- 主动抛出异常
- 有些时候,系统不会主动抛出异常,这就需要我们主动抛出异常-throw
-
throw异常后,会在当前函数中查找匹配的catch,找不到就 终止当前函数代码,去上一层函数中查找。如果最终都找不 到匹配的catch,整个程序就会终止(注意理解!!!)
int divide(int a, int b) { //因为b=0时,系统不会抛出异常,因此,只能我们主动抛出异常 //if (!b) throw 6; if (!b) throw "字符串异常"; return a / b; } int main() { cout << "1" << endl; try{ int v1 = 10; int v2 = 0; cout << divide(v1, v2) << endl; }catch (int exception){//捕捉整形异常 cout << "发生了异常-"<<exception << endl; }catch (const char *exception) {//捕捉字符串异常 cout << "发生了异常-"<<exception << endl; } cout << "2" << endl; getchar(); return 0; }
- 异常的抛出声明
-
为了增强可读性和方便团队协作,如果函数内部可能会抛出异常,建议函数声明一下异常类型
//抛出任何可能的异常 void func1() { } //不抛出任何异常 void func2() throw() { } //只抛出int、double类型的异常 void func3() throw(int,double) { } //别人调用 try{ func3(); }catch ( int exp) { }catch (double exp){ }
-
- 自定义异常类型
-
有时候觉得系统自带的异常没有明确的意思,这就需要我们自己定义异常
//自定义异常 //基础类 class Exception { public : //纯虚函数 virtual string what() const = 0; }; //除法错误异常 class DivideException :public Exception { public: string what()const { return "不能除以0!"; } }; //加法错误异常 class AddException :public Exception { }; int divide(int v1, int v2) throw(Exception) { if (v2 == 0) throw DivideException(); return v1 / v2; } int main() { try{ int a = 10; int b = 0; int c = divide(a, b); //const 是即可以接受常量,也可以接受非常量 }catch (const Exception &exception){ //常量调用方法,必须是常量方法,所以what()是常量方法 cout << exception.what() << endl; } }
-
-
标准异常(std)
- 这些事系统定义的异常
- 代码举例:
try { for (int i = 0; i < 9999999; i++) { int *p = new int[9999999]; } }catch (std:: bad_alloc exception) { //bad_alloc:内存分配异常 cout << exception.what() << endl; }
智能指针(Smart Pointer)
- 传统指针存在的问题
- 需要手动管理内存
- 容易发生内存泄露(忘记释放、出现异常等)
- 释放之后产生野指针
- 智能指针就是为了解决传统指针存在的问题
- auto_ptr:属于C++98标准,在C++11中已经不推荐使用(有缺陷,比如不能用于数组)
- shared_ptr:属于C++11标准
- unique_ptr:属于C++11标准
-
auto_ptr的使用
#include<iostream> using namespace std; class Person { public: int m_age = 10; Person() { cout << "Person()" << endl; } ~Person() { cout << "~Person()" << endl; } }; int main() { { //1. 在堆中创建一个对象:这么写,新创建的Person对象时不会被释放的,必须手动释放 Person *p = new Person(); //2. 必须手动释放 delete p; //3. p必须还得清空,否则在使用p的时候,会出现野指针错误; //cout << p->m_age << endl; p = nullptr; //注意了,这里的p仅仅是一个栈中的指针变量,当前的{}就是p的作用域,一旦},p就会被销毁 } //智能指针auto_ptr { //创建一个auto_ptr智能指针p,存放Peron对象的地址 //那么这个p也是一个栈中的指针变量,作用域也是当前的{}; //智能指针的好处:一旦智能指针p销毁,那么它当初指向的这个对象也会被释放 auto_ptr<Person> p(new Person()); //此时还没有被销毁,还是可以用的 cout << p->m_age << endl; } //不能适用于数组 { auto_ptr<Person> p(new Person[5]{}); //这个会发现,掉用了5次构造函数,只调用了一次析构函数 //说明这个智能指针的原理就是 delete p; } getchar(); return 0; }
-
智能指针的简单自实现
//泛型 template<class T> class SmartPointer { T* m_pointer; public: SmartPointer(T *pointer):m_pointer(pointer){} ~SmartPointer(){ if (m_pointer == nullptr) return; delete m_pointer; } //重载运算符:通过智能指针访问对象成员 T *operator->() { return m_pointer; } }; int main() { { //创建智能指针 SmartPointer<Person> p(new Person()); //重载-> //等价于:p->operator()->m_age; cout << p->m_age << endl; } getchar(); return 0; }
- 由此可见,智能指针本质就是一个类模板!!!
- shared_ptr
- shared_ptr的设计理念
- 多个shared_ptr可以指向同一个对象,当最后一个shared_ptr在作用域范围内结束时,对象才会被自动释放
- 代码示例:
-
代码
int main() { cout << "1" << endl; { //创建一个智能指针p2 shared_ptr<Person> p2; { shared_ptr<Person> p1(new Person()); //一个对象可以被多个智能指针指向 p2 = p1; cout << "2" << endl; }//p1销毁 cout << "3" << endl; }//这个时候,p2销毁 cout << "4" << endl; }
-
打印:
//打印 1 Person() 2 3 ~Person() 4
- 上面是执行过程,为何析构在3/4之间?
- 因为在3/4之间的那个},完成p1,p2都销毁了,对象才会被释放
-
-
针对数组的用法
int main() { { //这么写也跟auto一样,5个构造,1个析构 //shared_ptr<Person>p(new Person[5]{}); //这么写才可以,要多传一个表达式-Lambda表达式 //原理:当智能指针要销毁所指对象的时候,他就会将它指向的地址传递给Lambda表达式,然后调用Lambda表达式的代码 //shared_ptr<Person>p(new Person[5]{}, [](Person *P) {delete[] P; }); //另外一种写法 shared_ptr<Person[]>p(new Person[5]{}); } getchar(); return 0; }
- shared_ptr的原理
- 一个shared_ptr会对一个对象产生强引用(strong reference)
- 每个对象都有个与之对应的强引用计数器,记录着当前对象被多少个shared_ptr强引用着
- 可以通过shared_ptr的use_count函数(这个函数是编译器默认存在的)获得强引用计数
- 当有一个新的shared_ptr指向对象时,对象的强引用计数就会+1
- 当有一个shared_ptr销毁时(比如作用域结束),对象的强引用计数就会-1
- 当一个对象的强引用计数为0时(没有任何shared_ptr指向对象时),对象就会自动销毁(析构)
-
代码举例:
int main() { { shared_ptr<Person> p1(new Person()); cout << p1.use_count() << endl; { shared_ptr<Person> p2(p1); cout << p1.use_count() << endl; } cout << p1.use_count() << endl; { shared_ptr<Person> p3(p1); cout << p1.use_count() << endl; } cout << p1.use_count() << endl; { shared_ptr<Person> p4(p1); //use_count函数是编译器默认的 cout << p1.use_count() << endl; } cout << p1.use_count() << endl; } getchar(); return 0; }
- shared_ptr的注意点
-
不要使用裸指针来初始化智能指针,比如以下代码
Person *p = new Person();//这种叫裸指针:没有被智能指针包着的指针 { //用裸指针初始化智能指针p1 shared_ptr<Person> p1(p); } { shared_ptr<Person> p2(p); } //打印: //Person() //~Person() //~Person() //会发现析构2次,不对
-
可以通过一个已存在的智能指针初始化一个新的智能指针
shared_ptr<Person> p1(new Person()); shared_ptr<Person> p2(p1);
-
- shared_ptr的循环引用
-
循环引用的代码如下:
//提前声明 class Person; class Car { public : //Person *m_person; shared_ptr<Person> m_person = nullptr; Car() { cout << "Car()" << endl; } ~Car() { cout << "~Car()" << endl; } }; class Person { public: //Car *m_car; shared_ptr<Car> m_car = nullptr; int m_age = 10; Person() { cout << "Person()" << endl; } ~Person() { cout << "~Person()" << endl; } void run() { cout << "run()" << endl; } }; int main() { /*Person *p = new Person(); p->m_car = new Car(); p->m_car->m_person = p;*/ { shared_ptr<Person>person(new Person()); shared_ptr<Car>car(new Car()); person->m_car = car; car->m_person = person; //打印会发现,car和person都没有调用各自的析构函数 } getchar(); return 0; }
-
循环引用的原理:
-
- shared_ptr的设计理念
- weak_ptr
- weak_ptr会对一个对象产生弱引用,
-
weak_ptr可以指向对象解决shared_ptr的循环引用问题
//提前声明 class Person; class Car { public : //Person *m_person; //一方使用弱引用即可:若引用不能被初始化!!! weak_ptr<Person> m_person; }; class Person { public: //Car *m_car; shared_ptr<Car> m_car = nullptr; }; int main() { { shared_ptr<Person>person(new Person()); shared_ptr<Car>car(new Car()); person->m_car = car; car->m_person = person; } getchar(); return 0; }
- unique_ptr
- unique_ptr也会对一个对象产生强引用,它可以确保同一时间只有1个指针指向对象
- 当unique_ptr销毁时(作用域结束时),其指向的对象也就自动销毁了
-
可以使用std::move函数转移unique_ptr的所有权
int main() { { unique_ptr<Person> p1(new Person()); //不能使用,同一时间只能被一个指针引用 //unique_ptr<Person> p2(p1); //但是可以转移所有权,此时p2指向了这个对象 unique_ptr<Person> p2 = std::move(p1); } getchar(); return 0; }