【发布时间】:2015-04-22 06:16:00
【问题描述】:
我是一名学习 C++ 的 C 程序员。在 C 中,有一个常见的goto idiom used to handle errors and exit cleanly from a function。我读过在面向对象程序中首选通过try-catch 块处理异常,但我在用 C++ 实现这种范例时遇到了麻烦。
以 C 中的以下函数为例,它使用 goto 错误处理范例:
unsigned foobar(void){
FILE *fp = fopen("blah.txt", "r");
if(!fp){
goto exit_fopen;
}
/* the blackbox function performs various
* operations on, and otherwise modifies,
* the state of external data structures */
if(blackbox()){
goto exit_blackbox;
}
const size_t NUM_DATUM = 42;
unsigned long *data = malloc(NUM_DATUM*sizeof(*data));
if(!data){
goto exit_data;
}
for(size_t i = 0; i < NUM_DATUM; i++){
char buffer[256] = "";
if(!fgets(buffer, sizeof(buffer), fp)){
goto exit_read;
}
data[i] = strtoul(buffer, NULL, 0);
}
for(size_t i = 0; i < NUM_DATUM/2; i++){
printf("%lu\n", data[i] + data[i + NUM_DATUM/2]);
}
free(data)
/* the undo_blackbox function reverts the
* changes made by the blackbox function */
undo_blackbox();
fclose(fp);
return 0;
exit_read:
free(data);
exit_data:
undo_blackbox();
exit_blackbox:
fclose(fp);
exit_fopen:
return 1;
}
我尝试使用异常处理范例在 C++ 中重新创建函数:
unsigned foobar(){
ifstream fp ("blah.txt");
if(!fp.is_open()){
return 1;
}
try{
// the blackbox function performs various
// operations on, and otherwise modifies,
// the state of external data structures
blackbox();
}catch(...){
fp.close();
return 1;
}
const size_t NUM_DATUM = 42;
unsigned long *data;
try{
data = new unsigned long [NUM_DATUM];
}catch(...){
// the undo_blackbox function reverts the
// changes made by the blackbox function
undo_blackbox();
fp.close();
return 1;
}
for(size_t i = 0; i < NUM_DATUM; i++){
string buffer;
if(!getline(fp, buffer)){
delete[] data;
undo_blackbox();
fp.close();
return 1;
}
stringstream(buffer) >> data[i];
}
for(size_t i = 0; i < NUM_DATUM/2; i++){
cout << data[i] + data[i + NUM_DATUM/2] << endl;
}
delete[] data;
undo_blackbox();
fp.close();
return 0;
}
我觉得我的 C++ 版本没有正确实现异常处理范例;事实上,由于随着函数的增长,catch 块中累积的清理代码的构建,C++ 版本似乎更不可读且更容易出错。
我已经读到,由于称为 RAII 的东西,catch 块中的所有这些清理代码在 C++ 中可能是不必要的,但我不熟悉这个概念。 我的实现是否正确,还是有更好的方法来处理错误并干净地退出 C++ 中的函数?
【问题讨论】:
-
如果
data的大小是一个很小的编译时常数,为什么要动态分配它?如果您愿意,请使用不需要显式删除的std::vector,无论函数如何返回或抛出。您也不需要显式关闭文件流。将你的整个函数放在一个 try 块中,并根据一些逻辑(一个标志或捕获不同的异常类型)决定是否调用 undo_blackbox。 -
明确查找 RAII。例如,您的
fp.close()是不必要的。使用 std::vector,您的数据删除也应该过时了。 -
RAII 的重点是:当
blackbox()抛出时,你不需要调用fp.close()(或者根本不需要,事实上)——ifstream的析构函数会为你做这件事.使用scope guard 在异常退出时自动运行undo_blackbox()。将data设为std::vector<unsigned long>- 无论函数正常退出还是异常退出,它的析构函数都会自动释放内存。一旦你通过 RAII 管理所有资源,你就很少会写try/catch块 - 你只会允许传播异常。 -
在整个节目中实现 RAII 模式之前,我认为明智的做法是停下来问问自己“当未处理的异常向上传播时运行此清理代码是否合理?”这种模式一直让我觉得非常危险!如果您预料到异常,您会处理它。所以如果异常没有处理,很可能是意料之外的,所以你不知道自己程序的状态。在这种情况下运行更多代码真的明智吗?这就像一颗炸弹在午餐时引爆,在你撤离大楼之前,你要洗碗。
-
@EricLippert 好吧,唯一合理的做法是中止......您不会因为损坏的状态而抛出异常,当您的状态损坏时您会中止。未损坏但无效状态的例外情况(例如在没有空闲内存时分配内存;而不是在空闲块列表损坏时分配内存)。
标签: c++ c exception error-handling exception-handling