Reputation: 26356
My problem is that I'm writing a program which is supposed to be readable for the future, and the program has a lot of exception cases. So whenever I have to throw an exception, I have to writer over 10 lines to initialize my exception class and append the information from the program to it. For example as follows:
MyExceptionClass ex;
ex.setErrorMessage("PIN_CANNOT_GO_IN");
ex.setErrorDetails("The pin is asked to go to the state IN while the depth of the r-coordinate does not support it");
ex.setResolutionMessage("Configure your coordinates-file to move first to the correct position before changing the state of the pin");
ex.VariableList().resize(5);
ex.VariableList()[0].push_back("Pin state: ");
ex.VariableList()[0].push_back(ToString(pin.getPinState()));
ex.VariableList()[1].push_back("Pin target state: ");
ex.VariableList()[1].push_back(ToString(coordinatesData[coordinatesIndex].targetPinState));
ex.VariableList()[2].push_back("Current r Value: ");
ex.VariableList()[2].push_back(ToString(EncoderPosition.r));
ex.VariableList()[3].push_back("Current phi Value: ");
ex.VariableList()[3].push_back(ToString(EncoderPosition.phi));
ex.VariableList()[4].push_back("Current z Value: ");
ex.VariableList()[4].push_back(ToString(EncoderPosition.z));
ex.printLog();
ex.writeLog(exceptionLogFilePath.getValue());
throw ex;
So for only 5 variables, I had to write all that... Is there an efficient way to contain all information from the program (variables at least) and not rewrite all this every time I wanna throw an exception?
Thanks in advance.
Upvotes: 3
Views: 247
Reputation: 381
You can use Boost Exception to streamline adding arbitrary data to your exception objects, and to augment them with more relevant data as they bubble up the call stack. You won't have to worry about pre-defining everything you might need to store in exceptions, as you can literally store any data as needed to any exception.
Upvotes: 1
Reputation: 385325
Those textual descriptions should be linked to the exception class inherently, rather than written into each instance as runtime data.
Similarly, all that informational data should be members of the exception class, and you can format it for output as text later (perhaps in a member function of the exception class itself).
Upvotes: 0
Reputation: 300209
I had a similar issue: how does one enrich an exception with contextual information ?
Boost proposes one solution: try/catch
and enrich the exception in the catch
block before rethrowing it. It does enrich the exception, but not automatically.
The solution I came up with in the end is extremely simple, and based on C++ destructors strength. But first, how to use it:
void foo(int i) {
LOG_EX_VAR(i);
// do something that might throw
}
Yep, that's all, a single macro call and i
is added to an exception context along with the function name, file name and line number the macro was expanded at.
What's behind ? The most important const and a bit of magic.
class LogVar {
public:
LogVar(LogVar const&) = delete;
LogVar& operator=(LogVar const&) = delete;
virtual ~LogVar() {}
protected:
LogVar();
}; // class LogVar
template <typename T>
class LogVarT: public LogVar {
public:
LogVarT(char const* fc, char const* fl, int l, char const* n, T const& t):
_function(fc), _filename(fl), _line(l), _name(n), _t(t) {}
~LogVar() {
ContextInterface::AddVariable(_function, _filename, _line, _name, _t);
}
private:
char const* _function;
char const* _filename;
int _line;
char const* _name;
T const& _t;
}; // class LogVarT
template <typename T>
LogVarT make_log_var(char const* fc,
char const* fl,
int l,
char const* n,
T const& t)
{
return LogVarT(fc, fl, l, n, t);
}
#define LOG_EX_VAR(Var_) \
LogVar const& BOOST_PP_CAT(_5416454614, Var_) = \
make_log_var(__func__, __FILE__, __LINE__, #Var_, Var_);
This works fairly well, if you can get the difficult part (the ContextInterface::AddVariable()
function) to work.
If you don't want to bother with it, then go for a thread_local
std::vector<LogVar*>
as you did. Just be aware that you'll be doing a lot of work for nothing.
If you are interested in it, then follow on.
thread_local
). But even then one might accidentally leak a reference to it outside.catch
clause.So, let's get the interface straight:
class ContextInterface {
public:
typedef std::unique_ptr<ContextInterface> UPtr;
typedef std::shared_ptr<ContextInterface> SPtr;
typedef std::weak_ptr<ContextInterface> WPtr;
static UPtr SetDefault(UPtr d) {
std::swap(d, DefaultContext);
return d;
}
template <typename T, typename... Args>
static SPtr SetActive(Args&&... args) {
SPtr ci = ExceptionContext.lock();
if (ci.get()) { return ci; }
ci.reset(new T(std::forward<Args>(args)...));
ExceptionContext = ci;
return ci;
}
template <typename T>
static void AddVariable(char const* fc,
char const* fl,
int l,
char const* n,
T const& t)
{
SPtr sp = ExceptionContext.lock();
ContextInterface* ci = sp.get();
if (not ci) { ci = DefaultContext.get(); }
if (not ci) { return; }
ci->report(fc, fl, l, n) << t;
}
virtual ~ContextInterface() {}
private:
static thread_local UPtr DefaultContext;
static thread_local WPtr ExceptionContext;
virtual std::ostream& report(char const* fc,
char const* fl,
int l,
char const* n) = 0;
}; // class ContextInterface
And finally, the last missing piece (well, apart from the actual contexts you would want I guess): an example of the base exception class.
class ContextualException: public virtual std::exception {
public:
ContextualException(): _c(ContextInterface::SetActive<ExceptionContext>()) {}
ContextInterface const& context() const { return *_c; }
private:
ContextInterface::SPtr _c;
}; // class ContextualException
Upvotes: 0
Reputation: 26356
I think I got the cleanest way to do it. Please let me hear what you think.
So I encapsulate all the relevant variables in a templatized class as follows (just a quick-and-dirty example)
class VarBase
{
VarBase();
static std::vector<VarBase*> __allParams;
string getStringValue() = 0;
};
template <typename T>
class Var : public VarBase
{
T value;
string name;
string description;
toString();
operator T();
string getStringValue();
};
VarBase::VarBase()
{
__allParams.push_back(this);
}
VarBase::~VarBase()
{
//handle removing from __allParams vector or whatever container
}
template <typename T>
std::string Var<T>::getStringValue()
{
std::stringstream s;
s << paramValue;
return s.str();
}
Now if the my exception class is friends with the VarBase class, it can access __allParams and loop through that and call getStringValue() which will automatically do convert the value to a string and add it to my exception calss when necessary :)
Any additional ideas is highly appreciated.
Upvotes: 0
Reputation: 11075
If the data added to the exception class is only used to display an error message, you could use string concatenation to reduce the number of push_back()
used.
For example, you could use:
ex.VariableList()[0].push_back(string("Pin state: ") + ToString(pin.getPinState());
You could even concatenate all the other messages instead of using separate indices (1, 2, 3, 4, etc.) for each.
Moreover, for each field, you could use a dedicated setter method to feed the appropriate value. For example:
ex.VariableList()[0].setPinState(ToString(pin.getPinState()));
and then the "Pin state: "
part should be moved to the place where the error message is printed.
Going even further, your exception class could have a dedicated method which accepts all the objects which contribute to the error message, and call that message instead. For example:
void MyExceptionClass::setMessage(Pin& pin, CoordinatesData& cd, EncoderPosition& ep) {
setPinState(ToString(pin.getPinState()));
// set whatever else you want here
}
Moreover, move the ToString()
part to wherever the message is getting printed, just store the values in the exception class. For example, change the line above to (you need to change the signature accordingly):
setPinState(pin.getPinState());
and let the printing logic decide how to convert it to string. A further advantage is that it allows you to print the same message in different formats.
Upvotes: 2
Reputation: 1771
You could use a common function (fill_out_exception_parameters) that fills out the VariableList object for a generic exception and re-use that in any new exception classes that you write
Upvotes: 4