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

时间:2013-02-10 09:45:04

标签: c++ exception coding-style readability throw

我的问题是我正在编写一个应该可读的程序,并且程序有很多例外情况。因此,每当我必须抛出异常时,我必须编写超过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个变量,我不得不写下所有...... 是否有一种有效的方法来包含程序中的所有信息(至少是变量)​​,并且每次我想抛出异常时都不会重写所有这些信息?

提前致谢。

6 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分: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());

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

答案 2 :(得分:1)

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

答案 3 :(得分: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(),这将自动将值转换为字符串并在必要时将其添加到我的异常calss :)

非常感谢任何其他想法。

答案 4 :(得分:0)

我遇到了类似的问题:如何通过上下文信息来充实异常?

Boost提出了一个解决方案:try/catch并在重新抛出之前在catch块中丰富异常。它确实丰富了异常,但不是自动

我最终提出的解决方案非常简单,并且基于C ++析构函数的强度。但首先,如何使用它:

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

是的,就是这样,一个宏调用和i被添加到异常上下文中,同时扩展了宏的函数名,文件名和行号。

背后是什么? most important 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_);

如果您可以使困难部分(ContextInterface::AddVariable()功能)起作用,这种方法效果相当不错。

如果您不想打扰它,请转到thread_local std::vector<LogVar*>。请注意,你将无所事事地做很多工作。

如果您对此感兴趣,请继续。

  1. 让我们意识到这里最重要的部分是获得线程安全的东西。所以上下文将是全局的...每个线程(又名thread_local)。但即使这样,人们也可能不小心在外面泄漏了对它的引用。
  2. 重要的是要意识到有几个例外可能共存,但在任何时间点都只有一个例外未被捕获:这可能会在catch条款中抛出异常。
  3. 我们只能检测自己抛出的异常,因此我们需要某种默认策略。例如,记录。
  4. 所以,让我们直接接口:

    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
    

    最后,最后一个缺失的部分(好吧,除了你想要的实际上下文之外):基本异常类的一个例子。

    class ContextualException: public virtual std::exception {
    public:
        ContextualException(): _c(ContextInterface::SetActive<ExceptionContext>()) {}
    
        ContextInterface const& context() const { return *_c; }
    
    private:
        ContextInterface::SPtr _c;
    }; // class ContextualException
    

答案 5 :(得分:0)

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

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