RAII vs. exceptions

后端 未结 7 1377
小鲜肉
小鲜肉 2021-01-29 21:54

The more we use RAII in C++, the more we find ourselves with destructors that do non-trivial deallocation. Now, deallocation (finalization, however you want to call it) can fail

7条回答
  •  慢半拍i
    慢半拍i (楼主)
    2021-01-29 22:41

    From the original question:

    Now, deallocation (finalization, however you want to call it) can fail, in which case exceptions are really the only way to let anybody upstairs know of our deallocation problem

    Failure to cleanup a resource either indicates:

    1. Programmer error, in which case, you should log the failure, followed by notifying the user or terminating the application, depending on application scenario. For example, freeing an allocation that has already been freed.

    2. Allocator bug or design flaw. Consult the documentation. Chances are the error is probably there to help diagnose programmer errors. See item 1 above.

    3. Otherwise unrecoverable adverse condition that can be continued.

    For example, the C++ free store has a no-fail operator delete. Other APIs (such as Win32) provide error codes, but will only fail due to programmer error or hardware fault, with errors indicating conditions like heap corruption, or double free, etc.

    As for unrecoverable adverse conditions, take the DB connection. If closing the connection failed because the connection was dropped -- cool, you're done. Don't throw! A dropped connection (should) result in a closed connection, so there's no need to do anything else. If anything, log a trace message to help diagnose usage issues. Example:

    class DBCon{
    public:
      DBCon() { 
        handle = fooOpenDBConnection();
      }
      ~DBCon() {
        int err = fooCloseDBConnection();
        if(err){
          if(err == E_fooConnectionDropped){
            // do nothing.  must have timed out
          } else if(fooIsCriticalError(err)){
            // critical errors aren't recoverable.  log, save 
            //  restart information, and die
            std::clog << "critical DB error: " << err << "\n";
            save_recovery_information();
            std::terminate();
          } else {
            // log, in case we need to gather this info in the future,
            //  but continue normally.
            std::clog << "non-critical DB error: " << err << "\n";
          }
        }
        // done!
      }
    };
    

    None of these conditions justify attempting a second kind of unwind. Either the program can continue normally (including exception unwind, if unwind is in progress), or it dies here and now.

    Edit-Add

    If you really want to be able to keep some sort of link to those DB connections that can't close -- perhaps they failed to close due to intermittent conditions, and you'd like to retry later -- then you can always defer cleanup:

    vector to_be_closed_later;  // startup reserves space
    
    DBCon::~DBCon(){
      int err = fooCloseDBConnection();
      if(err){
        ..
        else if( fooIsRetryableError(err) ){
          try{
            to_be_closed.push_back(handle);
          } catch (const bad_alloc&){
            std::clog << "could not close connection, err " << err << "\n"
          }
        }
      }
    }
    

    Very not pretty, but it might get the job done for you.

提交回复
热议问题