代码之家  ›  专栏  ›  技术社区  ›  Elazar Leibovich

C++异常的替代

  •  13
  • Elazar Leibovich  · 技术社区  · 14 年前

    我正在编写一个反应式软件,它反复接收输入、处理输入并发出相关输出。主循环类似于:

    initialize();
    while (true) {
        Message msg,out;
        recieve(msg);
        process(msg,out);
        //no global state is saved between loop iterations!
        send(out);
    }
    

    我希望无论在进程阶段发生什么错误,无论是内存不足错误、逻辑错误、无效断言等,程序都会清除它所做的一切,并继续运行。我将假设它是无效的输入,并简单地忽略它。

    C++的异常对这种情况特别好,我可以包围它。 process try/catch 子句,并在出现错误时抛出异常。我唯一需要确保的是在抛出异常之前清理所有资源。这可以通过RAII进行验证,或者通过编写一个全局资源分配器(例如,如果析构函数可能抛出一个异常),并将其专门用于所有资源。

    Socket s = GlobalResourceHandler.manageSocket(new Socket());
    ...
    try {
        process(msg,out);
    catch (...) {
        GlobalResourceHandler.cleanUp();
    }
    

    然而,在我们的编码标准中,使用exception是被禁止的(同样在 Google's C++ standard

    此外,这是嵌入式平台的代码,所以我们使用的C++额外功能越少,代码就越快,并且它越便携。

    更新:

    我还在找工作 技术的 回答。

    9 回复  |  直到 13 年前
        1
  •  8
  •   Matthieu M.    14 年前

    整天为这些服务编码我理解你的问题。尽管我们的代码中确实存在异常,但我们不会将它们返回到调用它的外部库,而是有一个简单的“tribool”。

    enum ReturnCode
    {
      OK = 0,  // OK (there is a reason for it to be 0)
      KO,      // An error occurred, wait for next message
      FATAL    // A critical error occurred, reboot
    };
    

    我必须说 FATAL 是。。。例外。除了初始化之外,应用程序中没有任何返回它的代码路径(如果没有正确初始化,则无法执行太多操作)。

    C++在这里带来了很多RAI,因为它笑了多个返回路径,并保证了它所持有的对象的确定性释放。

    // Here is the reason for OK being 0 and KO and Fatal being something else
    
    #define CHECK_RETURN(Expr) if (ReturnCode code = (Expr)) return code;
    
    #define CHECK_BREAK(Expr) if (ReturnCode code = (Expr)) \
        if (KO == code) break; else return code;
    

    然后你可以这样使用它们:

    CHECK_RETURN( initialize() )
    while(true)
    {
      Message msg,out;
      CHECK_BREAK( receive(msg) )
      CHECK_BREAK( process(msg,out) )
      CHECK_BREAK( send(out) )
    }
    

    如前所述,真正令人不快的是构造函数。在这种情况下,你不可能有“正常”的构造函数。

    boost::optional

    boost::optional<MyObject> obj = MyObject::Build(1, 2, 3);
    if (!obj) return KO;
    
    obj->foo();
    

    看起来很像指针,只是它是堆栈分配的,因此开销几乎为零。

        2
  •  6
  •   ChrisW    14 年前

    throw 一个例外,那么另一个选择就是 return (或 return false 或类似的错误代码)。

    无论是抛出还是返回,仍然使用C++确定性析构函数来释放资源。

    • 在每个构造函数之后调用一个“get\u isConstructedOk()”属性(不要忘记在每个新构造的对象上调用/检查它)
    • 实现“两阶段”构造:你说任何可能失败的代码都不能在构造函数中,而必须在单独的构造函数中 bool initialize() 在构造函数之后调用的方法(不要忘记调用 initialize 别忘了检查它的返回值)。
        3
  •  5
  •   utnapistim    14 年前

    谷歌的C++标准BTW。有 我可以考虑另一种设计吗?

    简单的回答是

    :). 您可以使所有函数返回错误代码(类似于Microsoft COM平台的实现)。

    • 你必须明确地处理所有例外情况

    而不是:

    initialize();
    while (true) {
        Message msg,out;
        recieve(msg);
        process(msg,out);
        //no global state is saved between loop iterations!
        send(out);
    }
    

    您有:

    if( !succeedded( initialize() ) )
        return SOME_ERROR;
    
    while (true) {
        Message msg,out;
        if( !succeeded( RetVal rv = recieve(msg) ) )
        {
             SomeErrorHandler(rv);
             break;
        }
        if( !succeeded( RetVal rv = process(msg,out) ) )
        {
             SomeErrorHandler(rv);
             break;
        }
        //no global state is saved between loop iterations!
        if( !succeeded( RetVal rv = send(out) ) )
        {
             SomeErrorHandler(rv);
             break;
        }
    }
    

    此外,所有函数的实现都必须执行相同的操作:用一个 if .

    在上面的示例中,还必须确定每次迭代的rv值是否构成当前函数的错误,并(最终)直接从 while ,或在出现任何错误时中断while,并返回值。

    简而言之,除了在代码和模板中可能使用RAII(你是否允许使用它们),你最终接近“C代码,使用C++编译器”。

    #define

    这是一个很好的案例,可以向贵公司负责编码标准的人员介绍:(

    编辑

        4
  •  4
  •   Troubadour    14 年前

    仅仅因为在您当前的编码标准中禁止使用异常,这并不意味着您应该在将来遇到类似这样的问题时立即消除它们。在这种情况下,您当前的编码标准可能没有设想出现这种情况。如果他们这样做了,他们可能会给你提供帮助,让你知道替代的实现方式是什么。

        5
  •  4
  •   Stephen C    14 年前

    像那样的编码标准简直是胡说八道。

        6
  •  0
  •   Pavel Radzivilovsky    14 年前

    如果在windows下运行,可以使用SEH异常。它们还具有堆栈前展开处理程序的优点,可以停止展开(异常\继续\执行)。

        7
  •  0
  •   Sparky    14 年前

    在我脑子里,你也许能用信号实现类似的效果。

    可能还有更多的事情要做,因为我还没有想清楚。

    希望这有帮助。

        8
  •  0
  •   Simon T.    14 年前

    你会调用任何可能引发异常的库吗?如果是这样的话,你还是需要试一试。对于内部错误,每个方法都需要返回一个错误代码(使用 return 仅对于错误代码,使用引用参数返回实际值)。如果您想使内存清理100%可靠,可以使用监视器应用程序启动应用程序。如果应用程序崩溃,监视器会重新启动它。你仍然需要关闭文件和数据库连接。

        9
  •  0
  •   Elazar Leibovich    14 年前

    另一种方法是,不抛出异常,而是设置一个全局错误指示符,并返回合法但任意的输入。然后在每个循环迭代中检查是否设置了全局错误指示符,如果设置了则返回。

    例如

    #define WHILE_R(cond,return_value) while (cond) {\
        if (exception_thrown) return return_value
    #define ENDWHILE() }
    
    bool isPolyLegal(Poly p) {
        PolyIter it(p);
        WHILE_R(it.next(),true) //return value is arbitary
        ...
            if (not_enough_memory) exception_thrown = true;
        ...
        ENDWHILE()
    }