C++-运行时类型信息,异常(day11)

一、运行时类型信息

1、typeid运算符

头文件:#include<typeinfo>

C++的标准头文件,都对应相应的类

//sizeof(类型/变量/表达式),返回内存大小

typeid(类型/变量/表达式),返回typeinfo类型的对象,其中包含name()成员函数,返回字符串,描述类型信息

虽然与函数调用形式相同,但是typeid是操作符。

int x;

cout<<typeid(int).name()<<endl;//不同编译器的字符串描述可能不同

cout<<typeid(x).name()<<endl;

cout<<typeid(int* [5]).name()<<endl;

cout<<typeid(int(*) [5]).name()<<endl;//数组指针
class X{

protected:

  virtual void foo(void){} 

};



class Y{

protected:

  void foo(void){}  

}



class Z{

  void foo(void){}

};

void func(X& x){

/*

  if(!strcmp(typeid(x).name(),"1Y")){

    cout<<"Y"<<endl;

  }

  

  else if(!strcmp(typeid(x).name(),"1Z")){

    cout<<"Z"<<endl;

  }

  else(!strcmp(typeid(x).name(),"1X")){

    cout<<"X"<<endl;

  }

*/
//typeinfo类中已经重载的operator==函数
  if(!strcmp(typeid(x).==typeid(Y))){

    cout<<"Y"<<endl;

  }

  

  else if(!strcmp(typeid(x)==typeid(Z))){

    cout<<"Z"<<endl;

  }

  else(!strcmp(typeid(x)==typeid(X)){

    cout<<"X"<<endl;

  }

}

注意:

  typeid的使用是基于基类中存在虚函数,并且子类继承并覆盖了虚函数。否则typeid无法获取类型信息。

2、动态类型转换运算符(day3)

目标类型变量=dynamic_cast<目标类型>目标源类型变量

使用场景:适用于具有多态继承关系的父子类指针或者引用之间的显式转换。

class A{virtual void foo(void){}};

class B:publicA{void foo(void)};

class C:publicA{void foo(void)};

class D{};



B b;

A* pa=&b;

//B* pb=pa;//向下转换,编译报错

//B* pb=static_cast<B*>(pa);//ok

B* pb=dynamic_cast<B*>(pa);//ok



//C* pc=static_cast<C*>(pa);//因为pa已经指向了B*的类型,虽然可以通过,但是不安全

 C* pc=dynamic_cast<C*>(pa);//程序执行阶段进行转换,而静态转换是程序编译阶段进行转换,虽然不报错,但是pc的地址将为空。执行时动态类型转换会做类型检查,如果是无关的类,无法转换。
D* pc=dynamic_cast<D*>(pa);//也为空

//可以打印出pa,pb,pc的地址,可见pc为空地址



//使用引用的不合理类型动态转换时,执行时会进程会被终止

dynamic_cast在转换的过程中,会根据多态的特性,检查父子类的指针或者引用目标类型是否一致,如果一致则转换成功,否则转换失败,如果是指针转换,则返回NULL,如果是引用转换,则抛出异常“bad_cast”


二、异常(Exception)

1、常见错误

  1)语法错误

  2)逻辑错误

  3)功能错误

  4)设计缺陷

  5)需求不符

  6)环境异常

  7)操作不当

2、C的错误处理机制

1)通过返回值表示错误

class A{

public:

  A(void){cout<<"A::A()"<<endl;}

  ~A(void){cout<<"~A::A()"<<endl;}

};

 

//通过返回值表示错误

int func3(void){

  A a;

  FILE* fp=fopen("none.text","r");

  if(fp==NULL){

    cout<<"file open error!"<<endl;

    return -1;

  }

  fclose(fp)

  return 0;

}

 

int func2(void){

  A a;

  if(func3()==-1){

    return -1;

  }

  return 0;

}

int func1(void){

  A a;

  if(func2()==-1){

    return -1;

  }

  //...

  retuern 0;

}

 

int main(void){

  if(func1()==-1){

    return -1;

  }

  //...

  return 0;

}

 




//通过返回值表示错误
栈区对象在出现异常之后,能够正常释放

2)通过远程跳转来处理错误



 
jmp_buf g_env;//包含头文件#include<setjmp.h>
class A{

public:

  A(void){cout<<"A::A()"<<endl;}

  ~A(void){cout<<"~A::A()"<<endl;}

};

 

//通过返回值表示错误

int func3(void){

  A a;

  FILE* fp=fopen("none.text","r");

  if(fp==NULL){

    longjmp(g_env,-1);
  }
  //...

  fclose(fp)

  return 0;

}

 

int func2(void){

  A a;

  func3();

  return 0;

}

int func1(void){

  A a;

  func2();


  //...

  retuern 0;

}

 

int main(void){

  if(setjmp(g_nev)==-1){//先设置g_env,如果有错误,会再次直接跳转到此处
    cout<<"file open error!"<<endl;
  }
  func1();   //...   return 0; }
 

使用远程跳转栈区对象无法得到释放

 

 通过返回值表示错误

  优点:函数调用路径中所有的局部对象都能够得到正常的析构,不会内存泄漏。

  缺点:错误处理流程比较复杂,逐层判断,代码臃肿

通过盐城跳转机制处理错误

  优点:不需要逐层判断,实现一步到位的错误处理,代码精简

  缺点:函数调用的路径中布局对象失去被析构的机会,形成内存析构

 3、C++的异常处理机制

   结合C中两种错误处理的优点,同时避免他们的缺点,在形式上实现一步到位的错误处理,无需逐层判断返回值,所有的局部对象得到正常的析构。

class A{

public:

  A(void){cout<<"A::A()"<<endl;}

  ~A(void){cout<<"~A::A()"<<endl;}

};

 

//通过返回值表示错误

int func3(void){

  A a;

  FILE* fp=fopen("none.text","r");

  if(fp==NULL){

    throw -1//抛出异常
  }
  //...

  fclose(fp)

  return 0;

}

 

int func2(void){

  A a;

  func3();

  return 0;

}

int func1(void){

  A a;

  func2();


  //...

  retuern 0;

}

 

int main(void){

 try{
    func1();
    //...出现异常,此处将不会得到执行,直接跳转到catch
  }
  catch(int ex/*抛出的异常数据类型*/){
    cout<<"file open error!"<<endl;
    return -1;
  }


  //...

  return 0;

}


如果执行到throw语句,会逐层返回执行},并且内存会得到释放

4、C++异常语法

(1)异常抛出

  throw 异常对象;//抛出的异常会被放到安全区,无法手动访问

如:

  throw -1;

  throw "file error";

  throw 对象;

  

(2)异常捕获

  try{

    //可能引发异常的语句

  }

  catch(异常类型1){

    //异常类型1的处理

  }

  catch(异常类型2){

    //异常类型2的处理

  }

  ...

  catch(.../*可以匹配任意类型*/){

    //针对其他类型的处理

  }

  

class FileError{
public:
  FileError(){}
  FileError(const string& file,int line):m_file(file),m_line(line){
    cout<<"抛出位置"<<m_file<<","<<m_line;
  }
private:
  string m_file;
  int m_line;
};
class A{

public:

  A(void){cout<<"A::A()"<<endl;}

  ~A(void){cout<<"~A::A()"<<endl;}

};

 

//通过返回值表示错误

int func3(void){

  A a;

  FILE* fp=fopen("none.text","r");

  if(fp==NULL){

    throw FileError(__FILE__,__LINE__);
    /*
    注意,是双下划线,而不是单下划线

    __FILE__ 包含当前程序文件名的字符串
    __LINE__ 表示当前行号的整数
    __DATE__ 包含当前日期的字符串
    __STDC__ 如果编译器遵循ANSI C标准,它就是个非零值
    __TIME__ 包含当前时间的字符串
     */
    
    //FileError ex;

    //throw ex;
    
    throw "file error";

    throw -1//抛出异常
  }
  //...

  fclose(fp)

  return 0;

}

 

int func2(void){

  A a;

  func3();

  return 0;

}

int func1(void){

  A a;

  func2();


  //...

  retuern 0;

}

 

int main(void){

 try{
    func1();
    //...出现异常,此处将不会得到执行,直接跳转到catch
  }
  catch(int ex/*抛出的异常数据类型*/){
    cout<<"file open error!"<<endl;
    return -1;
  }
  catch(const char* ex){
    cout<<"file error!"<<endl;
    return -1;
  }
  catch(FileError& ex){//如果抛出的是对象,最好是用引用
     cout<<"FileError"<<endl;
     return -1;
  }



  //...

  return 0;

}

注意:

  (1)如果没有类型可以匹配,那么抛出的异常将被系统锁捕获,进程将被回收。内存被释放

  (2)如果有两个连续的throw语句,只会被执行一个,因为在throw时候,直接跳转到}执行。

  (3)如果抛出的是类的对象,最好使用引用。

  (4)在面向对象的编程中,一般都是抛出对象,而不是整数或者字符串等基本类型,因为类比基本类型可以存储更多信息。比如日志等 

5、异常-扩展

class A{};

class B:public A{};

 

void func(void){

  //...

  throw(B);

  //throw(A);

  

}

 

int main(void){

  try{

    func();

  }

  catch(A& ex){//向上造型可以匹配B类异常

    cout<<"捕获到A类异常"<<endl;

    return -1;

  }

  catch(B& ex){

    cout<<"捕获到B类的异常"<<endl;

    return -1;

  }

  return 0;

}

//上述代码中B异常将无法被捕获,无论是抛A,还是抛B,正确的处理方式应该把子类的异常捕获放在基类之前,防止发生向上造型。

注意:

  catch的匹配是自上而下进行匹配,而不是选择最优匹配,所以应该把子类的异常捕获放在基类之前,防止发生向上造型。

6、异常说明
1)可以在函数原型中增加异常说明,说明该函数可能抛出的异常类型。提前通知编译器,函数会抛出的异常类型
 
 返回类型 函数名(形参表)[cosnt]throw(异常类型表){...}
  

  不加异常说明列表,异常也能够被正常捕获,和不加的区别在于,如果函数抛出了与说明列表不符的类型,这个异常将不会被捕获。自然也会被系统所捕获。

2)函数的异常说明只是一种承诺,表示该函数不会抛出说明列表意外的类型。意外的异常将会被系统所捕获。

3)如果不写异常说明,表示可以抛出任何异常

4)空异常说明,throw(),表示不会抛出任何异常。

5)如果函数的声明和定义分开,在声明和定义部分都要加上异常说明。并且说明列表必须相同,但是顺序可以改变。


7、异常说明与多态
class FileError{};
class MemError{};

class Base{
public:
  virtual void func(void)throw(FileError,MemError){}  
};

class Derived:public Base{
public:
  void func(void){}//虚函数覆盖会失败,因为子类的虚函数覆盖函数没有异常说明,这里异常说明范围可以缩小,但是不能扩大
};

如果基类中的虚函数带有异常说明,它的子类中,该函数的覆盖版本不能比基类版本抛出更多异常,否则编译器报出“放松throw限定”错误

原文地址:https://www.cnblogs.com/ptfe/p/11300823.html