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?
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
}