C++ 在C+中调度异常+;

C++ 在C+中调度异常+;,c++,oop,exception,C++,Oop,Exception,如何调度异常,以便以集中、用户友好的方式处理错误处理和诊断 例如: DataHW类处理与某些数据采集硬件的通信 DataHW类可能会基于一些可能的错误引发异常:间歇性信号、无信号、CRC故障、驱动程序错误。每种类型的错误都有自己的异常类 DataHW类由许多不同的代码段调用,这些代码段执行不同类型的采集和分析 正确的错误处理策略取决于异常类型和尝试的操作。(对于间歇性信号,请重试X次,然后告诉用户;对于驱动程序错误,请记录错误并重新启动驱动程序;等等)应如何调用此错误处理策略 将错误恢复编

如何调度异常,以便以集中、用户友好的方式处理错误处理和诊断

例如:

  • DataHW类处理与某些数据采集硬件的通信
  • DataHW类可能会基于一些可能的错误引发异常:间歇性信号、无信号、CRC故障、驱动程序错误。每种类型的错误都有自己的异常类
  • DataHW类由许多不同的代码段调用,这些代码段执行不同类型的采集和分析
正确的错误处理策略取决于异常类型和尝试的操作。(对于间歇性信号,请重试X次,然后告诉用户;对于驱动程序错误,请记录错误并重新启动驱动程序;等等)应如何调用此错误处理策略

  • 将错误恢复编码到每个异常类中:这将导致异常类非常大,并且包含高级UI和系统管理代码。这似乎很糟糕
  • 为每种类型的异常提供一个单独的
    catch
    块:因为DataHW类是从许多不同的地方调用的,所以每个
    catch
    块必须在每个调用站点复制。这似乎很糟糕
  • 使用单个
    catch
    块调用一些
    ExceptionDispatch
    函数,并使用基于RTTI的
    switch
    语句:RTTI和
    switch
    通常表示应用OO设计失败,但这似乎是最不坏的选择

通过捕获(…)并调用一个共享处理程序函数来避免在每个调用站点重复捕获块,该函数可重新调用和分派:

f()
{
    try
    {
        // something
    }
    catch (...)
    {
        handle();
    }
}

void handle()
{
    try
    {
        throw;
    }
    catch (const Foo& e)
    {
        // handle Foo
    }
    catch (const Bar& e)
    {
        // handle Bar
    }
    // etc
}

我经常遇到的一个想法是,异常应该由能够处理它们的级别捕获。例如,传输数据的函数可能捕捉到CRC错误,一旦捕捉到该异常,它可能会尝试重新传输,而“无信号”异常可能会在更高级别被捕捉,并放弃或延迟整个操作

但我的猜测是,这些异常中的大多数都将围绕同一个函数捕获。单独捕获和处理它们是一个好主意(如soln#2),但您认为这会导致大量重复代码(导致soln#3)

我的问题是,如果有很多代码需要复制,为什么不把它变成一个函数呢

我的想法是

void SendData(DataHW* data, Destination *dest)
{
    try {
        data->send(dest);
    } catch (CRCError) {
        //log error

        //retransmit:
        data->send(dest);
    } catch (UnrecoverableError) {
        throw GivingUp;
    }
}
我想这就像你的ExceptionDispatch()函数一样,只是它不会对异常类型进行
switch
ing操作,而是将异常生成调用本身包装起来,并捕获异常


当然,这个函数过于简化了——您可能需要一个围绕DataHW的完整包装器类;但我的观点是,如果类的不同用户处理异常的方式相似,最好有一个集中的点来处理所有DataHW异常。

我认为有三种方法可以解决这个问题

编写包装函数 为每个可以抛出异常的函数编写一个包装函数,该异常将处理异常。然后,所有调用方调用该包装器,而不是原始的抛出函数

使用函数对象 另一个解决方案是采用更通用的方法,编写一个函数来接受函数对象并处理所有异常。以下是一些例子:

class DataHW {
public:
    template<typename Function>
    bool executeAndHandle(Function f) {
        for(int tries = 0; ; tries++) {
            try {
                f(this);
                return true;
            }
            catch(CrcError & e) {
                // handle crc error
            }
            catch(IntermittentSignalError & e) {
                // handle intermittent signal
                if(tries < 3) {
                    continue;
                } else {
                    logError("Signal interruption after 3 tries.");
                } 
            }
            catch(DriverError & e) {
                // restart
            }
            return false;
        }
    }

    void sendData(char const *data, std::size_t len);
    void readData(char *data, std::size_t len);
};
因为您提供了函数对象,所以也可以管理状态。假设sendData更新len,以便它知道读取了多少字节。然后,您可以编写读取和写入的函数对象,并维护到目前为止读取的字符数

第二种方法的缺点是无法访问抛出函数的结果值,因为它们是从函数对象包装器调用的。没有简单的方法可以获得函数对象绑定器的结果类型。一种解决方法是编写一个结果函数对象,该对象在函数对象执行成功后由executeAndHandle调用。但是,如果我们在第二种方法中投入了太多的工作,只是为了让所有的内务工作顺利进行,那么结果就不值得了

两者结合 还有第三种选择。我们可以结合这两种解决方案(包装器和函数对象)

classdatahw{
公众:
模板
R executeAndHandle(函数f){
for(int尝试=0;尝试++){
试一试{
返回f(本);
}
捕获(CrcError&e){
//处理crc错误
}
捕捉(间歇性信号错误和e){
//处理间歇信号
如果(尝试次数<3){
继续;
}否则{
日志错误(“3次尝试后信号中断”);
} 
}
捕获(驱动错误和e){
//重新启动
}
//返回一个合理的默认值。对于bool,这是false。对于其他整数
//类型,它是零。
返回R();
}
}
发送数据(字符常量*数据,标准::大小长度){
返回executeAndHandle(
boost::bind(&DataHW::doSendData,_1,data,len));
}
//假设它在这个例子中返回了一些东西
T doSendData(字符常量*数据,标准::大小长度);
T数据读取数据(字符*数据,标准::大小长度);
};

技巧是
返回f()模式。即使f返回void,我们也可以返回。这最终将是我的最爱,因为它既允许将句柄代码集中在一个地方,也允许在包装器函数中进行特殊处理。您可以决定是否最好将其拆分,并创建一个拥有错误处理函数和包装器的类。这可能是一个更干净的解决方案(我想到这里。一个是基本的DataHW功能,一个是错误处理)。

也许您可以编写一个
void doit() {
    char buf[] = "hello world";
    hw.executeAndHandle(boost::bind(&DataHW::sendData, _1, buf, sizeof buf));
}
class DataHW {
public:
    template<typename R, typename Function>
    R executeAndHandle(Function f) {
        for(int tries = 0; ; tries++) {
            try {
                return f(this);
            }
            catch(CrcError & e) {
                // handle crc error
            }
            catch(IntermittentSignalError & e) {
                // handle intermittent signal
                if(tries < 3) {
                    continue;
                } else {
                    logError("Signal interruption after 3 tries.");
                } 
            }
            catch(DriverError & e) {
                // restart
            }
            // return a sensible default. for bool, that's false. for other integer
            // types, it's zero.
            return R();
        }
    }

    T sendData(char const *data, std::size_t len) {
        return executeAndHandle<T>(
            boost::bind(&DataHW::doSendData, _1, data, len));
    }

    // say it returns something for this example
    T doSendData(char const *data, std::size_t len);
    T doReadData(char *data, std::size_t len);
};