【问题标题】:How to delegate an action to function return?如何将动作委托给函数返回?
【发布时间】:2019-01-02 04:48:45
【问题描述】:

问题

我到处都出现了以下简单的情况。大量请求通过如下函数签名到达设备:

Err execute( const ICommandContext &context, 
         const RoutineArguments &arguments, 
         RoutineResults &results)

本质上是一个请求处理服务器,它将调用这个函数来执行具有这些签名的各种请求类型的函数。在出错的情况下,我们有 2 条返回路径。

  1. Err 输出类型(认为它等同于int)用于通知服务器或系统出现与系统有关的问题,而不是请求。在处理用户请求之前,它始终排在函数的顶部。
  2. RoutineResults 提供了一个setStatus 函数,可用于将请求的失败信息返回给客户端。

出于这个原因,我们弹出了很多此类代码:

// Failure due to request
Err error = someFunctionCall(clientInput);
if (!error.success()) {
    results.setStatus(error); // Inform the client of the error
    return SUCCESS; // Inform the system that we are all good
}

我们有一个特定的请求类型,它包含大约15 参数,这些参数在系统周围传入和发送。从概念上讲,我们需要 15 个 if error do 集合,这似乎很浪费。如果我们需要检查并更改返回方式的任何内容,也很容易出错。 我们如何有效地委派setStatus 并返回只需要在函数中发生一次的少量代码?

宏观解决方案

c 系统可能会使用宏来解决此问题,例如:

#define M_InitTry Err error
#define M_Try(statement) if (!(error = statement).success()) { goto catch_lab; }
#define M_Catch catch_lab: if (!error.successs())
#define M_Return return error

会这样使用:

Err execute( const ICommandContext &context, ...) {
    M_InitTry;

    ...

    M_Try(someFunctionCall(clientInput));
    M_Try(someFunctionCall(otherClientInput));
    ...

    M_Catch {
        // Other specific actions for dealing with the return.
        results.setStatus(error);
        error = SUCCESS;
    }
    M_Return;
}

这很好地清理了代码,但对于goto 并不是特别好。如果定义可能被goto 跳过的变量,则会导致问题。

委托解决方案

我试图想出更多 C++,所以我认为 RAII 类型的委托可能会有所帮助。比如:

class DelegateToFunctionEnd {

    typedef std::function<void(void)> EndFunction; 

    public: 
    DelegateToFunctionEnd(EndFunction endFunction) : callAtEnd(endFunction) { }

    ~DelegateToFunctionEnd() {
        callAtEnd();
    }

    private:         
    EndFunction callAtEnd;
};

很简单,它通过在析构函数中实现动作来执行动作的委托,直到函数返回。你可以这样使用它:

Err execute( const ICommandContext &context, ...) {
    Err error;
    DelegateToFunctionEnd del(std::bind(&RoutineResults::setStatus, &results, std::cref(error)));

    error = someFunctionCall(clientInput));
    if (error) return SUCCESS;

    ...
}

Live example. 这个解决方案看起来不错,但有几个问题:

  1. 目前还不清楚发生了什么。
  2. 正确设置错误更容易出错。
  3. 您仍然需要大量的if 语句来处理退货。
  4. 配置终止操作的能力不是很好。
  5. 如果用户在函数返回时没有仔细考虑项目的销毁顺序,就会很危险。

更好的解决方案?

这一定是一个经常出现的问题。是否有一个通用的解决方案可以提供该集合的干净委托并返回类型操作?


我在下面有一些不幸的限制。不要让这些阻止你回答,因为它可能对未来的人有帮助。

  1. 我正在开发一个 c++03 受限系统。我们有boost,但没有 c++11。
  2. 嵌入式系统,我们有关于异常和内存分配的愚蠢规则。

【问题讨论】:

  • 你能有一个function&lt;Err()&gt; 的数组,然后用你的检查遍历那个数组吗? for (auto&amp;&amp; f : functions) { auto error = f(); if (error) {/*..*/} } 之类的东西?
  • @Jarod42 可能是的,使用boost::function 实现会很有趣,但可能非常可行。您应该将此添加为答案。
  • 简短回答 - 看看 haskell Exception 是如何创建的。基本上它包装函数并根据发生的情况传播返回值或错误,直到它到达结束/错误处理程序。我会尽快给出更详细的答案

标签: c++ c++03


【解决方案1】:

如果错误状态代码很麻烦,您应该考虑改用异常。也就是说,改变你的函数的API

  • 所以他们保证作为后置条件取得成功
  • 在失败的情况下抛出一个合适的std::exception

如果您这样做,就不可能“忘记”检查状态代码。如果您选择不处理错误情况,低级代码抛出的异常会自动向上渗透。你只需要catch一个低级异常如果

  • 如果发生错误,您需要手动回滚或解除分配, RAII 是不切实际的。在这种情况下,您将重新抛出异常。
  • 您希望使用抛出的嵌套异常将低级异常消息或异常类型转换为高级消息。

【讨论】:

  • 是的,这太好了。不幸的是,它是一个嵌入式系统,我们没有例外规则,这不是很好(特别是因为这不是代码的关键部分)。但这无疑是对这个问题的一个很好的回答。
  • @FantasticMrFox 所以,在您的项目中,您编写的客户分配器是noexcept?因为如果你不这样做,很多 STL 都会抛出异常。
  • 我知道这两件事,并让人们意识到这一点。不幸的是,目前没有任何变化。我也在推动从 c++03 升级到 c++11。但是1个人推只能做这么多……
  • @FantasticMrFox 把这两个加起来。
【解决方案2】:

也许,您可以将语句写成数组,例如:

Err execute( const ICommandContext &context, ...)
{
    const boost::function<Err()> functions[] = {
        boost::bind(&someFunctionCall, std::ref(clientInput)),
        boost::bind(&someFunctionCall, std::ref(otherClientInput)),
        // ...
    };

    for (std::size_t i = 0; i != sizeof(functions) / sizeof(functions[0]); ++i) {
        Err err = functions[i]();

        if (!err.successs()) {
            results.setStatus(err);
            return SUCCESS;
        }
    }
    return SUCCESS;
}

如果你用不同的语句多次这样做, 你可以创建

Err execute_functions(const ICommandContext &context, std::function<Err()> functions);

根据您的需要,也许还可以提供其他入口点为OnError。

【讨论】:

    【解决方案3】:

    拆分功能。

    内部函数根据用户输入返回错误代码;外部将其转换为客户端错误,并且仅返回服务器端错误。

    内部函数包含:

    if(Err error = someFunctionCall(clientInput))
      return error;
    

    反复。外部有中继到客户端错误代码,但只有一次。

    Err 只需要一个运算符 bool。如果它不能拥有它,请创建一个与 Err 相互转换并具有运算符 bool 的类型。

    【讨论】:

    • 我认为这不是问题的答案......你不想再详细说明一下吗?
    • execute 的主体变为{ if (!server_validate(params...)) return FAILURE; do_execute(params); return SUCCESS; },其中server_validate 和do_execute 分别是您当前execute 的上半部分和下半部分
    • @Caleth 那么您只需将if error do 的大长线移动到server_validate 函数中。这很难解决问题,对吧?
    • @FantasticMrFox 编组从错误代码到一个位置的“大长线”?您在内部函数中执行if (blah) return x; 的位置很短。
    【解决方案4】:

    您能否向错误添加一个方法来执行检查等并返回一个布尔值。

    if(!someFunctionCall(clientInput).handleSuccess(results))
    {
        return SUCCESS;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-29
      • 1970-01-01
      相关资源
      最近更新 更多