c++exception

Try-catch block where managing exceptions could cause error


I have a School class which includes avl trees: one for students, another for emptyClasses, and another for nonEmptyclasses.

The function I'm struggling with looks like this: (status is an enum such that 0=succes,1=allocation_error,3=failure,4=invalid=input):

Status leave_class(int studentId){
   //check validity of input
   try{
      if(numStudents==1){
         #remove class from nonEmptyClasses
         #add class to emptyClasses
      }
      Class->removeStudent(studentId)
   }
   catch(...){
      Return status::allocation_error
   }
   Students->leave() //this line changes student fields to ints of 0 and nullptr for pointers
   Return status::successs
}

The 3 actions in the try block could throw exceptions/errors, but then in the catch block if I try to reverse the error that happened then the reversing process could also be prone to the same errors.

For example, if I succeeded in removing a class from nonEmptyClasses and failed to add it in the emptyClasses, I have to reverse the removal which could also end up with an error.

I thought of separating the actions into multiple functions, or using mltiple try blocks, but I keep going back to square 1.

Any recommendations?


Solution

  • Here are some pointers to how you could cleanup your code and add exception to status translations.

    #include <stdexcept>
    #include <functional>
    #include <iostream>
    
    enum class Status
    {
        Succes,
        AllocationError,
        Failure,
        InvalidInput
    };
    
    // You can have a reusable function for translating exceptions to your
    // status code. 
    Status TranslateException()
    {
        try
        {
            throw;
        }
        catch(const std::invalid_argument&) // <== replace this with your exception types
        {
            return Status::InvalidInput;
        }
        catch(const std::bad_alloc& )
        {
            return Status::AllocationError;
        }
        catch(std::exception&) // All exceptions should be derived from a base exception
        {
            return Status::Failure;
        }
        catch(...)
        {
            std::exit(-1); // This should really not happen
        }
    
        return Status::Succes;
    };
    
    
    // When dealing with exceptions Scopeguards are a very useful pattern 
    // this class will in its destructor call a function.
    // This is useful because if your function leaves a scope (through an exception)
    // a scopeguard instantiated in that scope will be deleted 
    // and its destructor will be called. Allowing you to do things.
    template<typename fn_t>
    class ScopeGuard
    {
    public:
        ScopeGuard(fn_t undo_function) : 
            m_undo_function{undo_function)
        {
        }
    
        ~ScopeGuard()
        {
            if (m_active) 
            {
                m_fn();
            }
        }
    
        // If you are at a point that
        // everything went will you can
        // release the guard (kind of like a transaction commit)
        void Release()
        {
            m_active = false;
        }
    
    private:
        bool m_active{true};
        fn_t m_undo_function;
    };
    
    // Now you can implement your function something like this
    Status leave_class(int studentId)
    {
        std::size_t numStudents{1ul};
    
        try
        {
            ScopeGuard undoRemoveFromNonEmptyClass = []{ std::cout << "Undo remove from non empty class here...\n" };
            ScopeGuard undoAddToEmptyClass = []{ std::cout << "Undo Add to empty class here...\n" };
            
            nonEmptyClasses.Remove(); 
            undoRemoveFromNonEmptyClass.Release();
    
            emptyClasses.Add();
            undoAddToEmptyClass.Release();
    
            Students->leave() //this line changes student fields to ints of 0 and nullptr for pointers
        }
        catch(const std::exception&)
        {
            return TranslateException();
        }
       
       return status::successs
    }