简体   繁体   English

如何在C ++中以干净的方式处理异常

[英]How to handle an exception in a clean way in C++

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. 因此,每当我必须抛出异常时,我必须编写超过10行来初始化我的异常类并将程序中的信息附加到它。 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? 所以对于只有5个变量,我必须编写所有这些...是否有一种有效的方法来包含程序中的所有信息(至少是变量)​​,并且每次我想抛出异常时都不会重写所有这些信息?

Thanks in advance. 提前致谢。

您可以使用一个公共函数(fill_out_exception_parameters)填充VariableList对象以获取一般异常,并在您编写的任何新异常类中重用它

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. 如果添加到异常类的数据仅用于显示错误消息,则可以使用字符串连接来减少使用的push_back()数。

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. 您甚至可以连接所有其他消息,而不是使用单独的索引(1,2,3,4等)。

Moreover, for each field, you could use a dedicated setter method to feed the appropriate value. 此外,对于每个字段,您可以使用专用的setter方法来提供适当的值。 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 . 然后应将"Pin state: "部分移动到打印错误消息的位置。


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. 此外,将ToString()部分移动到消息打印的任何位置,只需将值存储在异常类中。 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. 另一个优点是它允许您以不同的格式打印相同的消息。

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. 您可以使用Boost Exception简化向异常对象添加任意数据的过程,并在它们冒泡调用堆栈时使用更相关的数据来扩充它们。 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. 您不必担心预先定义可能需要存储在异常中的所有内容,因为您可以根据需要将任何数据存储到任何异常中。

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 :) 现在,如果我的异常类是VarBase类的朋友,它可以访问__allParams并循环遍历并调用getStringValue(),这将自动将值转换为字符串并在必要时将其添加到我的异常calss :)

Any additional ideas is highly appreciated. 任何其他想法都非常感谢。

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. Boost提出了一个解决方案: try/catch并在重新抛出之前丰富catch块中的异常。 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. 我最终提出的解决方案非常简单,并且基于C ++析构函数的强度。 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. 是的,这就是所有,一个宏调用, i被添加到异常上下文以及扩展宏的函数名,文件名和行号。

What's behind ? 背后是什么? The most important const and a bit of magic. 最重要的const和一点魔力。

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. 如果您可以使用困难的部分( ContextInterface::AddVariable()函数)工作,这种方法效果相当不错。

If you don't want to bother with it, then go for a thread_local std::vector<LogVar*> as you did. 如果你不想打扰它,那么就像你一样thread_local std::vector<LogVar*> Just be aware that you'll be doing a lot of work for nothing. 请注意,你将无所事事地做很多工作。

If you are interested in it, then follow on. 如果您对此感兴趣,请继续。

  1. Let's realize that the most important part here is to get something that is thread-safe. 让我们意识到这里最重要的部分是获得线程安全的东西。 So the context will be global... per thread (aka thread_local ). 所以上下文将是全局的......每个线程(又名thread_local )。 But even then one might accidentally leak a reference to it outside. 但即使这样,人们也可能不小心在外面泄漏了对它的引用。
  2. It is important to realize that several exceptions may coexist, though only one is uncaught at any point in time: that is an exception may be thrown within a catch clause. 重要的是要意识到几个例外可能共存,尽管在任何时间点只有一个例外未被捕获:这是一个异常可能在catch子句中抛出。
  3. We can only instrument the exceptions we throw ourselves, so we need some kind of default policy for the others. 我们只能检测自己抛出的异常,因此我们需要某种默认策略。 Logging, 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 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

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). 类似地,所有信息数据都应该是异常类的成员,并且您可以将其格式化为稍后输出(可能在异常类本身的成员函数中)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM