第十二章 异常、智能指针

异常

  1. 错误
    1. 编程过程中的常见错误类型
    2. 语法错误
    3. 逻辑错误
    4. 异常
  2. 异常
    1. 异常是一种在程序运行过程中的发生的不好预测的错误(比如内存不够)
    2. 异常没有被处理,会导致程序终止
    3. 系统异常:
      1. 某些时候写代码导致系统会主动抛出异常

         //系统抛出异常
         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;
        
    4. 主动抛出异常
      1. 有些时候,系统不会主动抛出异常,这就需要我们主动抛出异常-throw
      2. 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;
         }
        
    5. 异常的抛出声明
      1. 为了增强可读性和方便团队协作,如果函数内部可能会抛出异常,建议函数声明一下异常类型

         //抛出任何可能的异常
         void func1() {
                    
         }
         //不抛出任何异常
         void func2() throw() {
                    
         }
         //只抛出int、double类型的异常
         void func3() throw(int,double) {
          }
                     
          //别人调用
          try{
         	func3();
                    
         }catch ( int exp)
         {
                    
         }catch (double exp){
         }
        
    6. 自定义异常类型
      1. 有时候觉得系统自带的异常没有明确的意思,这就需要我们自己定义异常

         //自定义异常
         //基础类
         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;
         	}
         }
        
    7. 标准异常(std)

      图1

      1. 这些事系统定义的异常
      2. 代码举例:
       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)

  1. 传统指针存在的问题
    1. 需要手动管理内存
    2. 容易发生内存泄露(忘记释放、出现异常等)
    3. 释放之后产生野指针
  2. 智能指针就是为了解决传统指针存在的问题
    1. auto_ptr:属于C++98标准,在C++11中已经不推荐使用(有缺陷,比如不能用于数组)
    2. shared_ptr:属于C++11标准
    3. unique_ptr:属于C++11标准
  3. 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;
     }
    
  4. 智能指针的简单自实现

     //泛型
     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;
     }
    
    1. 由此可见,智能指针本质就是一个类模板!!!
  5. shared_ptr
    1. shared_ptr的设计理念
      1. 多个shared_ptr可以指向同一个对象,当最后一个shared_ptr在作用域范围内结束时,对象才会被自动释放
      2. 代码示例:
        1. 代码

           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;
           }
          
        2. 打印:

           //打印
           1
           Person()
           2
           3
           ~Person()
           4
          
          1. 上面是执行过程,为何析构在3/4之间?
          2. 因为在3/4之间的那个},完成p1,p2都销毁了,对象才会被释放
    2. 针对数组的用法

       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;
       }
      
    3. shared_ptr的原理
      1. 一个shared_ptr会对一个对象产生强引用(strong reference)
      2. 每个对象都有个与之对应的强引用计数器,记录着当前对象被多少个shared_ptr强引用着
        1. 可以通过shared_ptr的use_count函数(这个函数是编译器默认存在的)获得强引用计数
      3. 当有一个新的shared_ptr指向对象时,对象的强引用计数就会+1
      4. 当有一个shared_ptr销毁时(比如作用域结束),对象的强引用计数就会-1
      5. 当一个对象的强引用计数为0时(没有任何shared_ptr指向对象时),对象就会自动销毁(析构)
      6. 代码举例:

         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;
         }
        
    4. shared_ptr的注意点
      1. 不要使用裸指针来初始化智能指针,比如以下代码

         Person *p = new Person();//这种叫裸指针:没有被智能指针包着的指针
         {
         	//用裸指针初始化智能指针p1
         	shared_ptr<Person> p1(p);
         }
         {
         	shared_ptr<Person> p2(p);
         }
         //打印:
         //Person()
         //~Person()
         //~Person()
         //会发现析构2次,不对
        
      2. 可以通过一个已存在的智能指针初始化一个新的智能指针

         shared_ptr<Person> p1(new Person());
         shared_ptr<Person> p2(p1);
        
    5. shared_ptr的循环引用
      1. 循环引用的代码如下:

         //提前声明
         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;
         }
        
      2. 循环引用的原理: 图1

  6. weak_ptr
    1. weak_ptr会对一个对象产生弱引用,
    2. 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;
       }
      
  7. unique_ptr
    1. unique_ptr也会对一个对象产生强引用,它可以确保同一时间只有1个指针指向对象
    2. 当unique_ptr销毁时(作用域结束时),其指向的对象也就自动销毁了
    3. 可以使用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;
       }
      
Table of Contents