How to handle a pure exception in C ++

My problem is that I am writing a program that needs to be readable for the future, and there are many exceptions in the program. So whenever I have to throw an exception, I have to write more than 10 lines to initialize my exception class and add 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, in just 5 variables, I had to write all this ... Is there an efficient way to hide all information from the program (at least the variables) and not rewrite the whole thing every time I want to throw an exception?

Thanks in advance.

+3


source to share


6 answers


You can use a generic function (fill_out_exception_parameters) that fills a VariableList object for a generic exception and reuses that in any new exception classes you write



+4


source


If the data added to the exception class is only used to display an error message, you can use string concatenation to reduce the number of used ones push_back()

.

For example, you can use:

ex.VariableList()[0].push_back(string("Pin state: ") + ToString(pin.getPinState());

      

You can even concatenate all other posts instead of using separate indices (1, 2, 3, 4, etc.) for each.

Also, for each field, you can use a dedicated setter method to supply the appropriate value. For example:

ex.VariableList()[0].setPinState(ToString(pin.getPinState()));

      

and then the part has "Pin state: "

to be moved to the place where the error message is printed.




Going even further, your exception class might have a dedicated method that takes all objects that contribute to the error message and raises that message instead. For example:

void MyExceptionClass::setMessage(Pin& pin, CoordinatesData& cd, EncoderPosition& ep) {
    setPinState(ToString(pin.getPinState()));
    // set whatever else you want here
}

      

Also, move the part ToString()

to where the message is printed, just store the values ​​in the exception class. For example, change the line above (you need to change the signature accordingly):

setPinState(pin.getPinState());

      

and let the print logic decide how to convert it to string. An added benefit is that it allows the same message to be printed in different formats.

+2


source


You can use Boost Exception to make it easier to add arbitrary data to your exception objects and augment it with more relevant data as it bubbles the call stack. You don't have to worry about predefining whatever you might need to store in exceptions, as you can literally store any data as needed for any exception.

+1


source


I think I had the cleanest way to do this. Please let me hear what you think.

So, I encapsulate all relevant variables in the 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 my exception class is friendly with the VarBase class, it can access __allParams and pass it and call getStringValue () which will automatically convert the value to a string and add it to my exceptions if needed :)

Any additional ideas are greatly appreciated.

0


source


I had a similar problem: how to enrich the exception with contextual information?

Boost offers one solution: try/catch

and will enrich the exception in the block catch

before it is re- thrown . This enriches the exception, but not automatically.

The solution I came up with at the end is extremely simple and builds on the power of C ++ destructors. But first, how to use it:

void foo(int i) {
    LOG_EX_VAR(i);
    // do something that might throw
}

      

Yes that's all, one macro call and is i

added to the exception context along with the function name, file name and line number in which the macro was extended.

What's behind? most important constant and some 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 well enough if you can get the hard part (function ContextInterface::AddVariable()

).

If you don't want to worry about it, go to thread_local

std::vector<LogVar*>

as you do. Just keep in mind that you will be doing a lot of work for nothing.

If you are interested in this, then continue.

  • Let's face it, the most important part here is getting what is thread safe. So the context will be global ... per thread (aka thread_local

    ). But even then, you can accidentally miss the link to it from the outside.
  • It is important to understand that there may be several exceptions, although only one of them fails at any given time: this exception can be thrown within a proposal catch

    .
  • We can only use the exceptions we throw ourselves, so we need some kind of default policy for others. Registration, for example.

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, which I suppose you would want): an example of a 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

      

0


source


These textual descriptions should be inherently associated with the exception class, and not written to each instance as runtime data.

Likewise, all information data must be members of the exception class, and you can format it to be output as text later (perhaps in a member function of the exception class itself).

0


source







All Articles