C++无try-catch的异常捕获示例详解

try-catch

在c++中,我们可以非常方便的使用try catch来捕获异常

try {
  throw 1;
} catch (int x) {
  cout << "x " << x << endl;
  throw std::runtime_error("exception");
} catch (...) {
  cout << "exception" << endl;
}

你可能有时候 c++的try-catch没什么用

但事实上,在解决某些问题方面,c++的异常强过你自己打flag 判断 比如下面这个例子

void dep2() {
  cout << "ok" << endl;
  throw "error";
}
void dep1() {
  dep2();
  cout << "ok call dep2" << endl;
}
void func() {
  dep1();
  cout << "ok call dep1" << endl;
}
signed main() {
  try {
    func();
  } catch (...) {
    cout << "error" << endl;
  }
  return 0;
}

不使用try-catch 可以怎么写?

pair<int, string> dep2() {
  cout << "ok" << endl;
  return {-1, "error"};
  cout << "ret" << endl;
  return {0, ""};
}
pair<int, string> dep1() {
  auto[ret, err] = dep2();
  if (ret == -1) return {ret, err};
  cout << "ok call dep2" << endl;
  return {0, ""};
}
pair<int, string> func() {
  auto[ret, err] = dep1();
  if (ret == -1) return {ret, err};
  cout << "ok call dep1" << endl;
  return {0, ""};
}
signed main() {
  auto[ret, err] = func();
  if (ret == -1) cout << err << endl;
  return 0;
}

可以看出, try-catch还是可以帮助我们减少一些没必要的判段和代码

没有try-catch的日子

上述是c++的。那么。。。c语言怎么办呢? 那么,大抵是这样的 用返回值来充当状态码返回,传参留一个空位接受返回数据。

/**
* -1 error
*  0 ok
*    other
**/
int func(int parameter, int* ret) {}

goto 是什么?

goto 语句允许把控制无条件转移到同一函数内的被标记的语句在任何编程语言中,都不建议使用 goto 语句。因为它使得程序的控制流难以跟踪,使程序难以理解和难以修改。任何使用 goto 语句的程序可以改写成不需要使用 goto 语句的写法。E.W.Dijikstra 在1965年提出结构化程序设计来规避这种错误

goto 就没有他的优点的吗?

goto是个好东西,就是得小心用。

现在的建议是不使用goto,且把goto妖魔化了

确实可以使用 while for之类的东西来替代goto

但是在有些时候,goto还是有一丢丢作用的。

例如:

signed main() {
  for (int i = 1; i <= 1000; i++) {
    for (int j = 1; j <= 1000; j++) {
      for (int k = 1; k <= 1000; k++) {
        if (i == 11 && j == 22 && k == 555)
          goto end;
      }
    }
  }
  end:
  return 0;
}
signed main() {
  bool flag = true;
  for (int i = 1; i <= 1000 && flag; i++) {
    for (int j = 1; j <= 1000 && flag; j++) {
      for (int k = 1; k <= 1000 && flag; k++) {
        if (i == 11 && j == 22 && k == 555)
          flag = false;
      }
    }
  }
  return 0;
}

抛开程序本身不谈,有时候使用goto跳出多级循环的便利性,更胜一筹。

不同函数之间跳转

上述讲的goto只能在同一个函数内进行跳转,不能够跨函数跳转

setjmp 和 longjmp

setjmp

setjmp()函数保存关于调用环境的各种信息(通常是堆栈指针、 缓冲区env中的指令指针,可能是其他寄存器的值和信号掩码) 稍后由longjmp()使用。调用时,setjmp()返回0。(用man setjmp查看更详细的介绍)

创建本地的jmp_buf缓冲区并且初始化,用于将来跳转回此处。这个子程序保存程序的调用环境于env参数所指的缓冲区,env将被longjmp使用。如果是从setjmp直接调用返回,setjmp返回值为0。如果是从longjmp恢复的程序调用环境返回,setjmp返回非零值。

longjmp

在执行longjmp之时,传入一个保存好的jmp_buf和一个返回值,程序就会切换上下文跨函数的跳转到 之前设置的setjump处执行。

例子

jmp_buf env;
signed main() {
  int stat = 0;
  if ((stat = setjmp(env)) == 1) {
    printf("1");
    return 1;
  }
  printf("0");
  longjmp(env, 1);
  return 0;
}
// 结果:
>. 01

原理,进程是一个状态机,我们当前的执行时刻拥有的状态有 寄存器的值,pc指针,堆栈信息等。 和上下文切换一样,我们可以保存一下当前进程的信息在jmp_buf里面,然后,等longjmp调用时,再去以保存的时候的进程信息去“切换”掉当前的状态信息,实现了跳转执行的过程,由于这个和上下文切换还是有区别,是切换回去当前进程。所以保存一下必要的寄存器值就可以了。

无try-catch的异常捕获

利用宏替换和switch来匹配错误

可以简单的实现异常的捕获和跳转

(还是c++的try-catch更强大)

#include <bits/stdc++.h>
#include <setjmp.h>
struct EXP {
  jmp_buf buf;
  int stat;
};
#define BEGIN_EXP(exp) switch (setjmp(exp.buf))
#define TRY(body) case 0: body break;
#define CATCH(exp, body) case exp: body break;
#define THROW(exp, data) longjmp(exp.buf, data)
int main() {
  EXP exp;
  BEGIN_EXP(exp) {
    TRY({
      std::cout << " data" << std::endl;
      THROW(exp, 1);
    })
    CATCH(1, {
      std::cout << "error 1" << std::endl;
      THROW(exp, 2);
    })
    CATCH(2, {
      std::cout << "error 2" << std::endl;
      THROW(exp, 3);
    })
    CATCH(3, {
      std::cout << "error 2" << std::endl;
    })
  }
  return 0;
}
作者:amjieker

%s 个评论

要回复文章请先登录注册