August 04, 2025 • Jonathan Marler
About 23 minutes into his talk about Safe C++ [1], Bjarne shows a slide with this code:
void f ( const char * p ) { FILE * f = fopen ( p , "r" ) ; fclose ( f ) ; }
He shows this code to demonstrate how easy it is to miss resource cleanup. Any code that exits the function inside // use f that doesn’t also call fclose results in a leak. He provides a C++ equivalent with RAII to avoid this footgun:
class File_handle { FILE * p ; public : File_handle ( const char * pp , const char * r ) { p = fopen ( pp , r ) ; if ( p == 0 ) throw File_error ( pp , r ) ; } File_handle ( const string & s , const char * r ) { p = fopen ( s . c_str (), r ) ; if ( p == 0 ) throw File_error ( pp , r ) ; } ~ File_handle () { fclose ( p ) ; } } ; void f ( string s ) { File_handle fh { s , "r" } ; }
This sample is great at showing the benefits of RAII, but introduces some problems when it comes to error handling. In this example, Bjarne elects to throw an exception to propagate any error from calling fopen . Unfortunately, C++ exceptions have 3 problems:
Correctness: you don’t know if the exception type you’ve caught matches what the code throws Exhaustiveness: you don’t know if you’ve caught all exceptions the code can throw RAISI: try/catch requires a new scope which usually means you need RAISI (Resource Acquisition is Second Initialization)
In most applications it’s expected that failing to open a file is a normal error that should be handled in some way other than throwing an exception all the way up the stack. Let’s assume the proper way to handle the error in this case is to report it to stderr and return from the function. Let’s see how that looks in the original C code:
void f ( const char * p ) { FILE * f = fopen ( p , "r" ) ; if ( f == NULL ) { fprintf ( stderr , "failed to open file '%s', error=%d
" , p , errno ) ; return ; } fclose ( f ) ; }
... continue reading