3

我的问题是我正在编写一个将来应该可读的程序,并且该程序有很多异常情况。所以每当我必须抛出异常时,我必须编写超过 10 行来初始化我的异常类并将程序中的信息附加到它。例如如下:

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;

所以只有 5 个变量,我必须写下所有这些......

提前致谢。

4

6 回答 6

4

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

于 2013-02-10T09:57:37.020 回答
2

如果添加到异常类的数据仅用于显示错误消息,则可以使用字符串连接来减少使用次数push_back()

例如,您可以使用:

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

您甚至可以连接所有其他消息,而不是为每个消息使用单独的索引(1、2、3、4 等)。

此外,对于每个字段,您可以使用专用的 setter 方法来提供适当的值。例如:

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

然后将"Pin state: "零件移到打印错误信息的地方。


更进一步,您的异常类可能有一个专用方法,该方法接受所有导致错误消息的对象,并改为调用该消息。例如:

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

此外,将ToString()部件移动到打印消息的任何位置,只需将值存储在异常类中即可。例如,将上面的行更改为(您需要相应地更改签名):

setPinState(pin.getPinState());

并让打印逻辑决定如何将其转换为字符串。另一个优点是它允许您以不同的格式打印相同的消息。

于 2013-02-10T09:53:55.983 回答
1

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

于 2013-02-17T07:27:27.513 回答
0

我想我有最干净的方法来做到这一点。请让我听听你的想法。

所以我将所有相关变量封装在一个模板化类中,如下所示(只是一个简单粗暴的例子)

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();
}

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

任何其他想法都将受到高度赞赏。

于 2013-02-10T10:30:11.703 回答
0

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.

  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). 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.
  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
于 2013-02-10T14:01:52.133 回答
0

这些文本描述应该与异常类固有地相关联,而不是作为运行时数据写入每个实例。

类似地,所有信息数据都应该是异常类的成员,您可以将其格式化以便稍后输出为文本(可能在异常类本身的成员函数中)。

于 2013-02-10T14:11:37.840 回答